如何在运行时使用 GoogleMock 在 C++ 单元测试中模拟系统调用?
How to mock system call in C++ Unit Testing during runtime using GoogleMock?
我有一个用 C 语言编写的模块。其中 API 定义如下。
int ReadData(void* data, int data_size, char* filename, int offset)
在执行单元测试以覆盖全覆盖时,我需要命中 fseek 和 fread 系统调用的错误条件。
有人可以解释一下我如何在特定测试用例期间覆盖 fseek/fread 所以它不会调用系统调用而是为该测试用例调用 mock fseek/fread 吗?
ReadAPI.c
#include<stdio.h>
#include<stdlib.h>
int readdata(void* data,int size, int offset, char* filename)
{
if(data == NULL || size == 0 || filename == NULL)
{
return -1;
}
FILE* fp = fopen(filename,"rb");
if(fp == NULL)
{
return -1;
}
if(fseek(fp,offset,SEEK_SET) != 0)
{
fclose(fp);
return -1;
}
fread(data,size,1,fp);
if(ferror(fp))
{
fclose(fp);
return -1;
}
fclose(fp);
return 1;
}
readapi.h
#include<stdio.h>
#include<stdlib.h>
int readdata(void* data,int size, int offset, char* filename)
Test.cpp
#include<gtest/gtest.h>
#include"readapi.h"
TEST(test, Test1fseek)
{
// When fseek called in readdata API call mock fseek to hit fseek fail
// fseek(){return -1;}
char data[10] = {0};
int status = readdata(data,sizeof(data),0,"test.txt");
EXPECT_EQ(status, -1);
}
TEST(test, Test2fread)
{
//When fread called in readdata API call mock fread to hit read fail
// fread(){return -1;}
char data[10] = {0};
int status = readdata(data,sizeof(data),0,"test.txt");
EXPECT_EQ(status, -1);
}
int main()
{
return RUN_ALL_TEST();
}
如果您使用 gcc
/g++
或 clang
/clang++
,您可以使用链接器选项 --wrap=symbol
将调用重定向到您自己的版本这些功能。
您的包装函数将命名为 __wrap_symbol
,而实际函数将命名为 __real_symbol
。
示例:
int __wrap_fseek(FILE *stream, long offset, int whence) {
printf("__wrap_fseek: %ld %d\n", offset, whence);
return __real_fseek(stream, offset, whence);
}
size_t __wrap_fread(void *restrict ptr, size_t size, size_t nmemb,
FILE *restrict stream)
{
return __real_fread(ptr, size, nmemb, stream);
}
用g++ ... -Wl,--wrap=fseek,--wrap=fread
编译
可以参考mocking-free-functions Gmock的文档
不幸的是,使用推荐的方法意味着您将不得不更改您的代码,如果您的代码需要严格成为 C
代码,这可能不起作用。
但是,如果您愿意接受这一点,根据文档,您将必须围绕您正在使用的所有系统功能创建一个包装器,然后模拟该包装器。
此外,不要忘记您必须为所有预期被调用的函数添加 EXPECT_CALL
或 ON_CALL
,并且 return 和 non-default(即 0) 值。例如,在您的第一个测试中,您还应该为 fopen
.
提供 EXPECT_CALL
或 ON_CALL
这是一个示例实现:
// System wrapper interface class.
class MySystemWrapperInterface {
public:
virtual FILE* fopen(const char* filename, const char* mode) = 0;
virtual int fseek(FILE* stream, long int offset, int whence) = 0;
virtual size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) = 0;
virtual int ferror(FILE* stream) = 0;
virtual int fclose(FILE* stream) = 0;
};
// System wrapper actual class used in production code.
class MySystemWrapperClass : public MySystemWrapperInterface {
public:
FILE* fopen(const char* filename, const char* mode) {
return ::fopen(filename, mode);
}
int fseek(FILE* stream, long int offset, int whence) {
return ::fseek(stream, offset, whence);
}
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
return ::fread(ptr, size, nmemb, stream);
}
int ferror(FILE* stream) { return ::ferror(stream); }
int fclose(FILE* stream) { return ::fclose(stream); }
};
// Mocked System wrapper used for testint.
class MySystemWrapperMockClass : public MySystemWrapperInterface {
public:
MOCK_METHOD(FILE*, fopen, (const char*, const char*), (override));
MOCK_METHOD(int, fseek, (FILE*, long int, int), (override));
MOCK_METHOD(size_t, fread, (void*, size_t, size_t, FILE*), (override));
MOCK_METHOD(int, ferror, (FILE*), (override));
MOCK_METHOD(int, fclose, (FILE*), (override));
};
// Wrapper class for your own readdata function.
class MyClass {
// The system wrapper passed by dependency injection through constructor.
MySystemWrapperInterface* system_wrapper_;
public:
// Initialize the system wrapper in constructor.
MyClass(MySystemWrapperInterface* system_wrapper)
: system_wrapper_(system_wrapper) {}
int readdata(void* data, int size, int offset, char* filename) {
if (data == NULL || size == 0 || filename == NULL) {
return -1;
}
FILE* fp = system_wrapper_->fopen(filename, "rb");
if (fp == NULL) {
return -1;
}
if (system_wrapper_->fseek(fp, offset, SEEK_SET) != 0) {
system_wrapper_->fclose(fp);
return -1;
}
system_wrapper_->fread(data, size, 1, fp);
if (system_wrapper_->ferror(fp)) {
system_wrapper_->fclose(fp);
return -1;
}
system_wrapper_->fclose(fp);
return 1;
}
};
TEST(test, Test1fseek) {
// Create the mock object and inject it into your class.
MySystemWrapperMockClass mock_system_wrapper;
MyClass my_object(&mock_system_wrapper);
// When fseek called in readdata API call mock fseek to hit fseek fail
// fseek(){return -1;}
// IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
// that are expected to be called.
EXPECT_CALL(mock_system_wrapper, fopen)
.Times(1)
.WillOnce(Return(reinterpret_cast<FILE*>(0x1)));
EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(1));
EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));
char data[10] = {0};
int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
EXPECT_EQ(status, -1);
}
TEST(test, Test2fread) {
// Create the mock object and inject it into your class.
MySystemWrapperMockClass mock_system_wrapper;
MyClass my_object(&mock_system_wrapper);
// When fread called in readdata API call mock fread to hit read fail
// fread(){return -1;}
// IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
// that are expected to be called.
EXPECT_CALL(mock_system_wrapper, fopen)
.Times(1)
.WillOnce(Return(reinterpret_cast<FILE*>(0x1)));
EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(0));
EXPECT_CALL(mock_system_wrapper, fread).Times(1).WillOnce(Return(-1));
EXPECT_CALL(mock_system_wrapper, ferror).Times(1).WillOnce(Return(-1));
EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));
char data[10] = {0};
int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
EXPECT_EQ(status, -1);
}
我有一个用 C 语言编写的模块。其中 API 定义如下。
int ReadData(void* data, int data_size, char* filename, int offset)
在执行单元测试以覆盖全覆盖时,我需要命中 fseek 和 fread 系统调用的错误条件。 有人可以解释一下我如何在特定测试用例期间覆盖 fseek/fread 所以它不会调用系统调用而是为该测试用例调用 mock fseek/fread 吗?
ReadAPI.c
#include<stdio.h>
#include<stdlib.h>
int readdata(void* data,int size, int offset, char* filename)
{
if(data == NULL || size == 0 || filename == NULL)
{
return -1;
}
FILE* fp = fopen(filename,"rb");
if(fp == NULL)
{
return -1;
}
if(fseek(fp,offset,SEEK_SET) != 0)
{
fclose(fp);
return -1;
}
fread(data,size,1,fp);
if(ferror(fp))
{
fclose(fp);
return -1;
}
fclose(fp);
return 1;
}
readapi.h
#include<stdio.h>
#include<stdlib.h>
int readdata(void* data,int size, int offset, char* filename)
Test.cpp
#include<gtest/gtest.h>
#include"readapi.h"
TEST(test, Test1fseek)
{
// When fseek called in readdata API call mock fseek to hit fseek fail
// fseek(){return -1;}
char data[10] = {0};
int status = readdata(data,sizeof(data),0,"test.txt");
EXPECT_EQ(status, -1);
}
TEST(test, Test2fread)
{
//When fread called in readdata API call mock fread to hit read fail
// fread(){return -1;}
char data[10] = {0};
int status = readdata(data,sizeof(data),0,"test.txt");
EXPECT_EQ(status, -1);
}
int main()
{
return RUN_ALL_TEST();
}
如果您使用 gcc
/g++
或 clang
/clang++
,您可以使用链接器选项 --wrap=symbol
将调用重定向到您自己的版本这些功能。
您的包装函数将命名为 __wrap_symbol
,而实际函数将命名为 __real_symbol
。
示例:
int __wrap_fseek(FILE *stream, long offset, int whence) {
printf("__wrap_fseek: %ld %d\n", offset, whence);
return __real_fseek(stream, offset, whence);
}
size_t __wrap_fread(void *restrict ptr, size_t size, size_t nmemb,
FILE *restrict stream)
{
return __real_fread(ptr, size, nmemb, stream);
}
用g++ ... -Wl,--wrap=fseek,--wrap=fread
可以参考mocking-free-functions Gmock的文档
不幸的是,使用推荐的方法意味着您将不得不更改您的代码,如果您的代码需要严格成为 C
代码,这可能不起作用。
但是,如果您愿意接受这一点,根据文档,您将必须围绕您正在使用的所有系统功能创建一个包装器,然后模拟该包装器。
此外,不要忘记您必须为所有预期被调用的函数添加 EXPECT_CALL
或 ON_CALL
,并且 return 和 non-default(即 0) 值。例如,在您的第一个测试中,您还应该为 fopen
.
EXPECT_CALL
或 ON_CALL
这是一个示例实现:
// System wrapper interface class.
class MySystemWrapperInterface {
public:
virtual FILE* fopen(const char* filename, const char* mode) = 0;
virtual int fseek(FILE* stream, long int offset, int whence) = 0;
virtual size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) = 0;
virtual int ferror(FILE* stream) = 0;
virtual int fclose(FILE* stream) = 0;
};
// System wrapper actual class used in production code.
class MySystemWrapperClass : public MySystemWrapperInterface {
public:
FILE* fopen(const char* filename, const char* mode) {
return ::fopen(filename, mode);
}
int fseek(FILE* stream, long int offset, int whence) {
return ::fseek(stream, offset, whence);
}
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream) {
return ::fread(ptr, size, nmemb, stream);
}
int ferror(FILE* stream) { return ::ferror(stream); }
int fclose(FILE* stream) { return ::fclose(stream); }
};
// Mocked System wrapper used for testint.
class MySystemWrapperMockClass : public MySystemWrapperInterface {
public:
MOCK_METHOD(FILE*, fopen, (const char*, const char*), (override));
MOCK_METHOD(int, fseek, (FILE*, long int, int), (override));
MOCK_METHOD(size_t, fread, (void*, size_t, size_t, FILE*), (override));
MOCK_METHOD(int, ferror, (FILE*), (override));
MOCK_METHOD(int, fclose, (FILE*), (override));
};
// Wrapper class for your own readdata function.
class MyClass {
// The system wrapper passed by dependency injection through constructor.
MySystemWrapperInterface* system_wrapper_;
public:
// Initialize the system wrapper in constructor.
MyClass(MySystemWrapperInterface* system_wrapper)
: system_wrapper_(system_wrapper) {}
int readdata(void* data, int size, int offset, char* filename) {
if (data == NULL || size == 0 || filename == NULL) {
return -1;
}
FILE* fp = system_wrapper_->fopen(filename, "rb");
if (fp == NULL) {
return -1;
}
if (system_wrapper_->fseek(fp, offset, SEEK_SET) != 0) {
system_wrapper_->fclose(fp);
return -1;
}
system_wrapper_->fread(data, size, 1, fp);
if (system_wrapper_->ferror(fp)) {
system_wrapper_->fclose(fp);
return -1;
}
system_wrapper_->fclose(fp);
return 1;
}
};
TEST(test, Test1fseek) {
// Create the mock object and inject it into your class.
MySystemWrapperMockClass mock_system_wrapper;
MyClass my_object(&mock_system_wrapper);
// When fseek called in readdata API call mock fseek to hit fseek fail
// fseek(){return -1;}
// IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
// that are expected to be called.
EXPECT_CALL(mock_system_wrapper, fopen)
.Times(1)
.WillOnce(Return(reinterpret_cast<FILE*>(0x1)));
EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(1));
EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));
char data[10] = {0};
int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
EXPECT_EQ(status, -1);
}
TEST(test, Test2fread) {
// Create the mock object and inject it into your class.
MySystemWrapperMockClass mock_system_wrapper;
MyClass my_object(&mock_system_wrapper);
// When fread called in readdata API call mock fread to hit read fail
// fread(){return -1;}
// IMPORTANT: Don't forget to add EXPECT_CALL or ON_CALL for all functions
// that are expected to be called.
EXPECT_CALL(mock_system_wrapper, fopen)
.Times(1)
.WillOnce(Return(reinterpret_cast<FILE*>(0x1)));
EXPECT_CALL(mock_system_wrapper, fseek).Times(1).WillOnce(Return(0));
EXPECT_CALL(mock_system_wrapper, fread).Times(1).WillOnce(Return(-1));
EXPECT_CALL(mock_system_wrapper, ferror).Times(1).WillOnce(Return(-1));
EXPECT_CALL(mock_system_wrapper, fclose).Times(1).WillOnce(Return(1));
char data[10] = {0};
int status = my_object.readdata(data, sizeof(data), 0, "test.txt");
EXPECT_EQ(status, -1);
}