在使用Google单元测试时如何在C中存根FGET

在使用Google单元测试时如何在C中存根FGET,c,unit-testing,fgets,C,Unit Testing,Fgets,目前,我被指派对我在入门训练营中遇到的一些问题进行单元测试,我在理解“存根”或“模拟”的概念时遇到了问题 我使用的是Google单元测试,bootcamp中的问题用C语言解决 int validate_input(uint32_t *input_value) { char input_buffer[1024] = {0}; char *endptr = NULL; int was_read_correctly = 1; printf(

目前,我被指派对我在入门训练营中遇到的一些问题进行单元测试,我在理解“存根”或“模拟”的概念时遇到了问题

我使用的是Google单元测试,bootcamp中的问题用C语言解决

int validate_input(uint32_t *input_value)
{

char      input_buffer[1024] = {0}; 
char                *endptr = NULL;
int         was_read_correctly = 1;

printf("Give the value for which to print the bits: ");

/* 
* Presuming wrong input from user, it does not signal:
* - number that exceeds the range of uint_32 (remains to be fixed)
* For example: 4294967295 is the max value of uint_32 ( and this can be also confirmed by the output )
* If bigger numbers are entered the actual value seems to reset ( go back to 0 and upwards.)
*/

if (NULL == fgets(input_buffer, 1024, stdin)) 
{
    was_read_correctly = 0;
}
else
{
    if ('-' == input_buffer[0])
    {
            fprintf(stderr,"Negative number not allowed.\n");
            was_read_correctly = 0;
    }
}

errno = 0; 

if (1 == was_read_correctly)
{
    *input_value = strtol(input_buffer, &endptr, 10);

    if (ERANGE == errno) 
    {
        fprintf(stderr,"Sorry, this number is too small or too large.\n");
        was_read_correctly = 0;
    }
    else if (endptr == input_buffer)
    {
            fprintf(stderr,"Incorrect input.\n(Entered characters or characters and digits.)\n");
            was_read_correctly = 0;
    }
    else if (*endptr && '\n' != *endptr)
    {
            fprintf(stderr,"Input didn't get wholely converted.\n(Entered digits and characters)\n");
            was_read_correctly = 0;
    }

}
else
{
        fprintf(stderr,"Input was not read correctly.\n");
         was_read_correctly = 0;
}

return was_read_correctly;
}

我应该如何思考/规划在C中存根类似于fgets/malloc的函数的过程?如果不是太多的话,那么像这样的函数应该如何测试呢?

我已经通过以下方式解决了这个问题:

存根函数的头文件:

#ifndef STUBS_H_
#define STUBS_H_
    
#include "../src/p1.h"
    
char* fgets_stub(char *s, int size, FILE *stream);
    
#define fgets fgets_stub
    
#include "../src/p1.c"
    
char* fgets_RET;
   
#endif
#include "stubs.h"

      
char* fgets_stub(char *s, int size, FILE *stream)
{
    if (NULL != fgets_RET)
    {
        strcpy(s,fgets_RET);
    }
    return fgets_RET;
}
存根函数的实现:

#ifndef STUBS_H_
#define STUBS_H_
    
#include "../src/p1.h"
    
char* fgets_stub(char *s, int size, FILE *stream);
    
#define fgets fgets_stub
    
#include "../src/p1.c"
    
char* fgets_RET;
   
#endif
#include "stubs.h"

      
char* fgets_stub(char *s, int size, FILE *stream)
{
    if (NULL != fgets_RET)
    {
        strcpy(s,fgets_RET);
    }
    return fgets_RET;
}
如何在
test.cpp
中进行测试:

TEST(ValidateInput,CorrectionTest)
{
    uint32_t tester = 0;
    
    char* dummy_char = new char[NUM_OF_BITS];

    strcpy(dummy_char,"39131");

    cout<<dummy_char;

    fgets_RET = dummy_char;
    ASSERT_EQ(1,validate_input(&tester));

}
免责声明:这只是为GoogleTest模拟C函数的一种方法。当然还有其他方法

模拟C函数的问题在于GoogleTest的工作方式。它的所有酷功能都是基于派生C++类来模拟和重写它的方法的。这些方法也必须是虚拟的。但是C函数不是任何类的成员,更不用说是虚函数了

我们发现并成功使用它的方式提供了一种包装类,其中包含与C函数具有相同原型的方法。此外,该类将指向自身实例的指针作为静态类变量保存。从某种意义上说,这类似于单身模式,无论好坏,都有其特点

每个测试都实例化此类的一个对象,并将该对象用于公共检查

最后,C函数被实现为调用同一类型的单个实例方法的存根


假设我们有这些C函数:

//cfunction.h
#ifndef C_函数
#定义C_函数
外部“C”无效cf1(内部p1,无效*p2);
外部“C”int cf2(无效);
#恩迪夫
那么mocking类的头文件是:

//CFunctionMock.h
#ifndef C_函数\u模拟\u H
#定义C_函数
#包括“gmock/gmock.h”
#包括“gtest/gtest.h”
#包括“cfunction.h”
类C函数模拟
{
公众:
静态CFunctionMock*实例;
CFunctionMock(){
实例=此;
}
~CFunctionMock(){
实例=nullptr;
}
模拟法(void,cf1,(int p1,void*p2));
模拟法(int,cf2,(void));
};
#恩迪夫
这是mocking类的实现,包括替换C函数。所有函数都检查单个实例是否存在

//CFunctionMock.cpp
#包括“CFunctionMock.h”
CFunctionMock*CFunctionMock::instance=nullptr;
外部“C”无效cf1(内部p1,无效*p2){
断言(CFunctionMock::instance,nullptr);
CFunctionMock::instance->cf1(p1,p2);
}
外部“C”内部cf2(无效){
if(CFunctionMock::instance==nullptr){
添加_FAILURE()cf2();
}
在非void函数中,您不能使用
ASSERT\NE
,因为它通过简单的
return
退出错误。因此,对现有实例的检查更为详细。您也应该考虑返回一个好的默认值

现在我们开始编写一些测试

//SomeTest.cpp
#包括“gmock/gmock.h”
#包括“gtest/gtest.h”
使用::测试::;
使用::测试::返回;
#包括“CFunctionMock.h”
#包括“模块到测试h”
测试(AGoodTestSuiteName和GoodTestName){
模拟模拟;
期望调用(mock,cf1(u,u))
.次(0);
EXPECT\u调用(mock,cf2())
.will(Return(23));
//调用(或不调用)C函数的模块对测试的任何调用
//任何期待。。。
}

编辑

我再次阅读了这个问题,并得出结论,一个更直接的例子是必要的。所以,我们开始吧!我喜欢使用谷歌测试背后的魔力,因为它使扩展变得更容易。解决它感觉像是在对抗它

哦,我的系统是Windows 10和MinGW64

我是Makefiles的粉丝:

TESTS := Test

WARNINGLEVEL := -Wall -Wextra

CC := gcc
CFLAGS := $(WARNINGLEVEL) -g -O3

CXX := g++
CXXFLAGS := $(WARNINGLEVEL) -std=c++11 -g -O3 -pthread

LD := g++
LDFLAGS := $(WARNINGLEVEL) -g -pthread
LIBRARIES := -lgmock_main -lgtest -lgmock

GTESTFLAGS := --gtest_color=no --gtest_print_time=0

all: $(TESTS:%=%.exe)

run: all $(TESTS:%=%.log)

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

%.o: %.cpp
    $(CXX) $(CXXFLAGS) -I./include -c $< -o $@

%.exe: %.o
    $(LD) $(LDFLAGS) $^ -L./lib $(LIBRARIES) -o $@

%.log: %.exe
    $< $(GTESTFLAGS) > $@ || type $@

Test.exe: module_to_test.o FgetsMock.o
构建和运行测试 这是我的(Windows)控制台在构建和测试时的输出:

> make run
gcc -Wall -Wextra -g -O3 -c module_to_test.c -o module_to_test.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c FgetsMock.cpp -o FgetsMock.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c Test.cpp -o Test.o
g++ -Wall -Wextra -g -pthread Test.o module_to_test.o FgetsMock.o -L./lib -lgmock_main -lgtest -lgmock -o Test.exe
Test.exe --gtest_color=no --gtest_print_time=0 > Test.log || type Test.log
Input was not read correctly.
Negative number not allowed.
Input was not read correctly.
Sorry, this number is too small or too large.
Input didn't get wholely converted.
(Entered digits and characters)
rm Test.o
您可以看到C函数的
stderr
的输出

// FgetsMock.cpp

#include <stdio.h>

#include "FgetsMock.h"

FgetsMock* FgetsMock::instance = nullptr;

extern "C" char* fgets(char* str, int num, FILE* stream)
{
    if (FgetsMock::instance == nullptr)
    {
        ADD_FAILURE() << "FgetsMock::instance == nullptr";
        return 0;
    }

    return FgetsMock::instance->fgets(str, num, stream);
}
这是记录的日志,看看Makefile是如何产生的

Running main() from gmock_main.cc
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from ValidateInput
[ RUN      ] ValidateInput.CorrectInput
Give the value for which to print the bits: [       OK ] ValidateInput.CorrectInput
[ RUN      ] ValidateInput.InputOutputError
Give the value for which to print the bits: [       OK ] ValidateInput.InputOutputError
[ RUN      ] ValidateInput.NegativeInput
Give the value for which to print the bits: [       OK ] ValidateInput.NegativeInput
[ RUN      ] ValidateInput.RangeError
Give the value for which to print the bits: [       OK ] ValidateInput.RangeError
[ RUN      ] ValidateInput.CharacterError
Give the value for which to print the bits: [       OK ] ValidateInput.CharacterError
[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran.
[  PASSED  ] 5 tests.

由于stdout上的输出,它与Googletest的输出混淆了。

一个常见的定义:存根是在测试过程中替代另一个组件的一小段代码。使用存根的好处是它返回一致的结果,使测试更容易编写。而且,即使其他组件ENT还没有工作。那么,你展示的代码是一个问题吗?它没有按照你期望的方式工作吗?代码完全是功能性的。我刚刚被指派学习如何进行单元测试,并被告知开始在我以前做过的事情上进行实践。现在我在理解需要尝试一下。例如,假设代码有错误,我想知道如果fgets无法读取某些内容,如何进行if处理?更具体地说,如何存根fgets函数?代码有错误,我想知道如果fgets无法读取某些内容,如何进行if处理无法读取某些内容,请确定其无法读取某些内容的原因,并确定根本原因。
fgets()
C
标准的一部分,例如,可能是未定义行为的受害者,也可能是被其他方法误用的受害者。确保存根代码的所有组件部分都没有引入UB,即它们将传播可预测和定义良好的结果…如果特定的inpu集ts(如上所述,没有UB)导致意外结果,您已经发现了一个问题。您注意到它,记录它,注册一个错误报告,然后继续。@Ryker恐怕您误解了OP的问题。这是一个如何解决Googletest
// Test.cpp

#include "gmock/gmock.h"
#include "gtest/gtest.h"

using ::testing::_;
using ::testing::DoAll;
using ::testing::Ge;
using ::testing::NotNull;
using ::testing::Return;
using ::testing::ReturnArg;

#include "FgetsMock.h"

extern "C"
{
#include "module_to_test.h"
}

TEST(ValidateInput, CorrectInput)
{
    const char input[] = "42";
    const int input_length = sizeof input;
    FgetsMock mock;
    uint32_t number;

    EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
        .WillOnce(DoAll(
            CopyFromSource(input),
            ReturnArg<0>()
        ));

    int result = validate_input(&number);

    EXPECT_EQ(result, 1);
    EXPECT_EQ(number, 42U);
}

TEST(ValidateInput, InputOutputError)
{
    FgetsMock mock;
    uint32_t dummy;

    EXPECT_CALL(mock, fgets(_, _, _))
        .WillOnce(Return(nullptr));

    int result = validate_input(&dummy);

    EXPECT_EQ(result, 0);
}

TEST(ValidateInput, NegativeInput)
{
    const char input[] = "-23";
    const int input_length = sizeof input;
    FgetsMock mock;
    uint32_t dummy;

    EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
        .WillOnce(DoAll(
            CopyFromSource(input),
            ReturnArg<0>()
        ));

    int result = validate_input(&dummy);

    EXPECT_EQ(result, 0);
}

TEST(ValidateInput, RangeError)
{
    const char input[] = "12345678901";
    const int input_length = sizeof input;
    FgetsMock mock;
    uint32_t dummy;

    EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
        .WillOnce(DoAll(
            CopyFromSource(input),
            ReturnArg<0>()
        ));

    int result = validate_input(&dummy);

    EXPECT_EQ(result, 0);
}

TEST(ValidateInput, CharacterError)
{
    const char input[] = "23fortytwo";
    const int input_length = sizeof input;
    FgetsMock mock;
    uint32_t dummy;

    EXPECT_CALL(mock, fgets(NotNull(), Ge(input_length), stdin))
        .WillOnce(DoAll(
            CopyFromSource(input),
            ReturnArg<0>()
        ));

    int result = validate_input(&dummy);

    EXPECT_EQ(result, 0);
}
> make run
gcc -Wall -Wextra -g -O3 -c module_to_test.c -o module_to_test.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c FgetsMock.cpp -o FgetsMock.o
g++ -Wall -Wextra -std=c++11 -g -O3 -pthread -I./include -c Test.cpp -o Test.o
g++ -Wall -Wextra -g -pthread Test.o module_to_test.o FgetsMock.o -L./lib -lgmock_main -lgtest -lgmock -o Test.exe
Test.exe --gtest_color=no --gtest_print_time=0 > Test.log || type Test.log
Input was not read correctly.
Negative number not allowed.
Input was not read correctly.
Sorry, this number is too small or too large.
Input didn't get wholely converted.
(Entered digits and characters)
rm Test.o
Running main() from gmock_main.cc
[==========] Running 5 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 5 tests from ValidateInput
[ RUN      ] ValidateInput.CorrectInput
Give the value for which to print the bits: [       OK ] ValidateInput.CorrectInput
[ RUN      ] ValidateInput.InputOutputError
Give the value for which to print the bits: [       OK ] ValidateInput.InputOutputError
[ RUN      ] ValidateInput.NegativeInput
Give the value for which to print the bits: [       OK ] ValidateInput.NegativeInput
[ RUN      ] ValidateInput.RangeError
Give the value for which to print the bits: [       OK ] ValidateInput.RangeError
[ RUN      ] ValidateInput.CharacterError
Give the value for which to print the bits: [       OK ] ValidateInput.CharacterError
[----------] Global test environment tear-down
[==========] 5 tests from 1 test suite ran.
[  PASSED  ] 5 tests.