免责声明:这只是为 GoogleTest 模拟 C 函数的一种方式。当然还有其他方法。
模拟 C 函数的问题在于 GoogleTest 的工作方式。它所有很酷的功能都是基于派生一个 C++ 类来模拟和覆盖它的方法。这些方法也必须是虚拟的。但是 C 函数不是任何类的成员,仅是虚拟的。
我们找到并成功使用它的方式是提供一种包装类,其中包含与 C 函数具有相同原型的方法。此外,此类将指向自身实例的指针作为静态类变量。在某种意义上,这类似于 Singleton 模式,具有其所有特性,无论好坏。
每个测试实例化这个类的一个对象,并使用这个对象进行公共检查。
最后,C 函数被实现为调用单个实例的同类方法的存根。
假设我们有这些 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();
}
在非 void 函数上,您不能使用 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。
我是 Makefile 的粉丝:
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);
}
实施一些测试
以下是一些测试示例。不幸的是,要测试的模块使用了stdout 和stderr,它们不容易捕获和测试。您可能想阅读“死亡测试”或提供您自己的重定向方法。在内核中,函数的设计不是很好,因为它没有考虑到测试。
// 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);
}
构建和运行测试
这是我的 (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 的输出混淆。