第一部分:C/C++中的Mock和Stub区别
基础概念
在C/C++单元测试中:
-
Stub(打桩):提供预设的返回值
-
Mock(模拟):验证函数调用行为
实际代码示例
假设我们有一个文件操作模块:
// file_operations.h
#ifndef FILE_OPERATIONS_H
#define FILE_OPERATIONS_H
#include <stdbool.h>
typedef struct {
bool (*file_exists)(const char* filename);
int (*read_file)(const char* filename, char* buffer, int size);
} FileOperations;
bool process_config_file(const char* filename, FileOperations* ops);
#endif
// file_operations.c
#include "file_operations.h"
#include <stdio.h>
#include <string.h>
bool process_config_file(const char* filename, FileOperations* ops) {
if (!ops->file_exists(filename)) {
return false;
}
char buffer[256];
int bytes_read = ops->read_file(filename, buffer, sizeof(buffer));
return bytes_read > 0;
}
Stub(打桩)示例:
// test_stub.c
#include "file_operations.h"
#include <assert.h>
// Stub 函数 - 只提供预设返回值,不关心调用细节
static bool stub_file_exists(const char* filename) {
return true; // 总是返回文件存在
}
static int stub_read_file(const char* filename, char* buffer, int size) {
const char* config_content = "timeout=30\nretries=3";
strcpy(buffer, config_content);
return strlen(config_content); // 返回预设内容
}
void test_with_stubs() {
FileOperations stubs = {
.file_exists = stub_file_exists,
.read_file = stub_read_file
};
bool result = process_config_file("config.txt", &stubs);
assert(result == true); // 只验证最终结果
}
Mock(模拟)示例:
// test_mock.c
#include "file_operations.h"
#include <assert.h>
#include <string.h>
// Mock 结构体 - 记录调用信息
typedef struct {
int file_exists_call_count;
int read_file_call_count;
char last_filename[256];
bool file_exists_return;
int read_file_return;
} FileOperationsMock;
static FileOperationsMock mock_data = {0};
// Mock 函数 - 记录调用行为
static bool mock_file_exists(const char* filename) {
mock_data.file_exists_call_count++;
strncpy(mock_data.last_filename, filename, sizeof(mock_data.last_filename)-1);
return mock_data.file_exists_return;
}
static int mock_read_file(const char* filename, char* buffer, int size) {
mock_data.read_file_call_count++;
strncpy(mock_data.last_filename, filename, sizeof(mock_data.last_filename)-1);
if (mock_data.read_file_return > 0) {
const char* content = "timeout=30";
strncpy(buffer, content, size-1);
return strlen(content);
}
return mock_data.read_file_return;
}
void test_with_mocks() {
// 重置 Mock 数据
memset(&mock_data, 0, sizeof(mock_data));
mock_data.file_exists_return = true;
mock_data.read_file_return = 10;
FileOperations mocks = {
.file_exists = mock_file_exists,
.read_file = mock_read_file
};
bool result = process_config_file("config.txt", &mocks);
// Mock 验证:检查交互行为
assert(mock_data.file_exists_call_count == 1);
assert(mock_data.read_file_call_count == 1);
assert(strcmp(mock_data.last_filename, "config.txt") == 0);
assert(result == true);
}
使用Mock框架(如Google Mock)
对于C++,使用Google Mock框架更简单:
// mock_example.cpp
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class FileSystemInterface {
public:
virtual ~FileSystemInterface() = default;
virtual bool fileExists(const std::string& filename) = 0;
virtual int readFile(const std::string& filename, char* buffer, int size) = 0;
};
class MockFileSystem : public FileSystemInterface {
public:
MOCK_METHOD(bool, fileExists, (const std::string& filename), (override));
MOCK_METHOD(int, readFile, (const std::string& filename, char* buffer, int size), (override));
};
TEST(FileProcessingTest, ProcessConfigFile) {
MockFileSystem mock_fs;
// Stubbing: 预设返回值
EXPECT_CALL(mock_fs, fileExists("config.txt"))
.WillOnce(testing::Return(true));
EXPECT_CALL(mock_fs, readFile("config.txt", testing::_, testing::_))
.WillOnce(testing::Return(10));
// Mock验证: 自动验证调用次数和参数
// Google Mock会验证上面的EXPECT_CALL是否满足
}