如何在使用Google单元测试时在C中存根fget 要测试的模块模拟课实施一些测试构建和运行测试

如何解决如何在使用Google单元测试时在C中存根fget 要测试的模块模拟课实施一些测试构建和运行测试

目前,我已被分配去对我在入门训练营中遇到的一些问题进行单元测试,并且在理解“存根”或“模拟”的概念时遇到了困难。

我正在使用Google Unit Test,并且通过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之类的函数的过程?而且,如果不是太多,应该如何考虑测试这样的功能?

解决方法

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

模拟C函数的问题在于GoogleTest的工作方式。它所有的出色功能都基于派生一个C ++类进行模拟并覆盖其方法。这些方法也必须是虚拟的。但是C函数不是任何类的成员,更不用说是虚拟的了。

我们找到并成功使用的方法提供了一种包装器类,其中包括与C函数具有相同原型的方法。另外,该类将指向其自身实例的指针作为静态类变量保存。从某种意义上讲,无论好坏,它都类似于Singleton模式,具有所有特征。

每个测试都实例化此类的对象,并将该对象用于常规检查。

最后,C函数实现为存根(stub),该存根调用相同类型的单个实例的方法。


假设我们具有以下C函数:

// cfunction.h

#ifndef C_FUNCTION_H
#define C_FUNCTION_H

extern "C" void cf1(int p1,void* p2);

extern "C" int cf2(void);

#endif

然后,模拟类的头文件是:

// CFunctionMock.h

#ifndef C_FUNCTION_MOCK_H
#define C_FUNCTION_MOCK_H

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

#include "cfunction.h"

class CFunctionMock
{
public:
    static CFunctionMock* instance;

    CFunctionMock() {
        instance = this;
    }

    ~CFunctionMock() {
        instance = nullptr;
    }

    MOCK_METHOD(void,cf1,(int p1,void* p2));

    MOCK_METHOD(int,cf2,(void));

};

#endif

这是模拟类的实现,包括替换的C函数。所有功能都会检查单个实例是否存在。

// CFunctionMock.cpp

#include "CFunctionMock.h"

CFunctionMock* CFunctionMock::instance = nullptr;

extern "C" void cf1(int p1,void* p2) {
    ASSERT_NE(CFunctionMock::instance,nullptr);
    CFunctionMock::instance->cf1(p1,p2);
}

extern "C" int cf2(void) {
    if (CFunctionMock::instance == nullptr) {
        ADD_FAILURE() << "CFunctionMock::instance == nullptr";
        return 0;
    }

    return CFunctionMock::instance->cf2();
}

在非无效函数上,您不能使用ASSERT_NE,因为它会因简单的return的错误而退出。因此,对现有实例的检查会更加详细。您还应该考虑返回一个很好的默认值。

现在我们要写一些测试。

// SomeTest.cpp

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

using ::testing::_;
using ::testing::Return;

#include "CFunctionMock.h"

#include "module_to_test.h"

TEST(AGoodTestSuiteName,AndAGoodTestName) {
    CFunctionMock mock;

    EXPECT_CALL(mock,cf1(_,_))
        .Times(0);
    EXPECT_CALL(mock,cf2())
        .WillRepeatedly(Return(23));

    // any call of module_to_test that calls (or not) the C functions

    // any EXPECT_...
}

编辑

我再次阅读了这个问题,得出的结论是,必须有一个更直接的例子。所以我们开始吧!我喜欢使用Googletest背后的许多魔力,因为它使扩展变得非常容易。解决它就像在反对它。

哦,我的系统是带有MinGW64的Windows 10。

我是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

这些Makefile文件可轻松添加更多测试,模块,所有内容并记录所有选项。扩展到您喜欢的方式。

要测试的模块

为了不发出警告,我不得不扩展提供的源代码:

// module_to_test.c

#include <errno.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#include "module_to_test.h"

// all the rest is as in the OP's source...

当然,我们需要一个头文件:

// module_to_test.h

#include <stdint.h>

int validate_input(uint32_t *input_value);

模拟课

模仿类是根据上面的示例建模的。启用“添加”我添加了参数化操作的字符串。

// FgetsMock.h

#ifndef FGETS_MOCK_H
#define FGETS_MOCK_H

#include <cstring>

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

ACTION_P(CopyFromSource,source)
{
    memcpy(arg0,source,arg1);
}

class FgetsMock
{
public:
    static FgetsMock* instance;

    FgetsMock()
    {
        instance = this;
    }

    ~FgetsMock()
    {
        instance = nullptr;
    }

    MOCK_METHOD(char*,fgets,(char*,int,FILE*));
};

#endif

其实现文件简单明了,并提供了模拟的C函数。

// 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);
}

实施一些测试

以下是一些测试示例。不幸的是,要测试的模块使用stdoutstderr来捕获和测试不是那么简单。您可能想阅读有关“死亡测试”的信息,或提供自己的重定向方法。从根本上说,该功能的设计不是很好,因为它没有考虑测试。

// 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,ReturnArg<0>()
        ));

    int result = validate_input(&dummy);

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

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

    EXPECT_CALL(mock,0);
}

构建和运行测试

这是我(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的输出。

这是记录的日志,请参见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的输出混在一起。

,

我设法通过以下方式解决了这个问题:

存根函数的头文件:

#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,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));

}

如果要测试的人希望强制fgets返回NULL:

TEST(ValidateInput,CorrectionTest)
{
    uint32_t tester = 0;
    
    fgets_RET = NULL;

    ASSERT_EQ(0,validate_input(&tester));

}

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-