如何通过宏技巧将5个相似C测试文件合并为单个文件?
精简多份重复测试代码的宏方案
问题背景
我有5个C语言测试源文件,核心逻辑完全一致,仅存在一处名称差异:将代码中的fread分别替换为getline、mmap_getline、mmap_memchr和read,差异点包括:
- 最后一条
#include指令(如../src/readlines_fread.c) - 测试函数名(如
test_readlines_fread_success) TEST_CHECK中调用的函数(如readlines_fread)TEST_LIST中的测试条目名称和函数指针
原测试代码示例:
#include <assert.h> #include <stdio.h> #include <string.h> #include "acutest.h" #include "../src/FileBuf.h" #include "../src/readlines_fread.c" void test_readlines_fread_success(void) { /* The lines and line lengths are at most 1024 in sample1.txt. */ char buf[1024]; char *lines[1024]; size_t line_count = 0; FILE *const fp = fopen("test/sample.txt", "r"); TEST_ASSERT(fp); while (fgets(buf, sizeof buf, fp)) { buf[strcspn(buf, "\n")] = '\0'; TEST_ASSERT((lines[line_count] = strdup(buf))); ++line_count; } rewind(fp); FileBuf fbuf = {}; TEST_CHECK(readlines_fread(fp, &fbuf)); TEST_CHECK(fbuf.count == 555); TEST_CHECK(fbuf.size == 120635); for (size_t i = 0; i < fbuf.count; ++i) { TEST_CHECK((strcmp(lines[i], fbuf.lines[i].line) == 0)); } fclose(fp); } TEST_LIST = { { "test_readlines_fread_success", test_readlines_fread_success }, { nullptr, nullptr }, };
需要将这5份文件合并为单个文件,通过编译时的宏定义实现不同名称的替换。
解决方案:预处理器宏替换技巧
1. 修改测试文件为模板形式
将代码中所有与fread相关的名称替换为宏占位符,同时通过条件编译处理头文件引入:
#include <assert.h> #include <stdio.h> #include <string.h> #include "acutest.h" #include "../src/FileBuf.h" // 根据编译宏选择引入对应的实现文件 #if defined(READ_IMPL) && READ_IMPL == FREAD #include "../src/readlines_fread.c" #elif READ_IMPL == GETLINE #include "../src/readlines_getline.c" #elif READ_IMPL == MMAP_GETLINE #include "../src/readlines_mmap_getline.c" #elif READ_IMPL == MMAP_MEMCHR #include "../src/readlines_mmap_memchr.c" #elif READ_IMPL == READ #include "../src/readlines_read.c" #else #error "Must define READ_IMPL with valid value" #endif // 宏工具:拼接标识符和字符串化 #define STRINGIFY(x) #x #define CONCAT(x, y) x##y #define TEST_FUNC_NAME(prefix, impl) CONCAT(prefix, impl) #define TEST_FUNC_STR(prefix, impl) STRINGIFY(CONCAT(prefix, impl)) // 用宏生成测试函数名 void TEST_FUNC_NAME(test_readlines_, READ_IMPL)_success(void) { char buf[1024]; char *lines[1024]; size_t line_count = 0; FILE *const fp = fopen("test/sample.txt", "r"); TEST_ASSERT(fp); while (fgets(buf, sizeof buf, fp)) { buf[strcspn(buf, "\n")] = '\0'; TEST_ASSERT((lines[line_count] = strdup(buf))); ++line_count; } rewind(fp); FileBuf fbuf = {}; // 调用对应的readlines_*函数 TEST_CHECK(CONCAT(readlines_, READ_IMPL)(fp, &fbuf)); TEST_CHECK(fbuf.count == 555); TEST_CHECK(fbuf.size == 120635); for (size_t i = 0; i < fbuf.count; ++i) { TEST_CHECK((strcmp(lines[i], fbuf.lines[i].line) == 0)); } fclose(fp); } // 用宏生成TEST_LIST条目 TEST_LIST = { { TEST_FUNC_STR("test_readlines_", READ_IMPL)"_success", TEST_FUNC_NAME(test_readlines_, READ_IMPL)_success }, { nullptr, nullptr }, };
2. 定义宏常量枚举(可选,提升可读性)
可创建头文件read_impl.h,定义各个实现的常量:
#define FREAD 1 #define GETLINE 2 #define MMAP_GETLINE 3 #define MMAP_MEMCHR 4 #define READ 5
编译时通过-include read_impl.h引入,或直接在编译命令中定义。
3. 编译命令示例
针对每个实现,通过-D参数指定READ_IMPL的值:
- 编译fread版本:
gcc -DREAD_IMPL=FREAD -I. test_readlines_template.c -o test_fread
- 编译getline版本:
gcc -DREAD_IMPL=GETLINE -I. test_readlines_template.c -o test_getline
- 编译mmap_getline版本:
gcc -DREAD_IMPL=MMAP_GETLINE -I. test_readlines_template.c -o test_mmap_getline
- 编译mmap_memchr版本:
gcc -DREAD_IMPL=MMAP_MEMCHR -I. test_readlines_template.c -o test_mmap_memchr
- 编译read版本:
gcc -DREAD_IMPL=READ -I. test_readlines_template.c -o test_read
关键宏说明
CONCAT(x, y):用于拼接标识符(如readlines_和FREAD拼接为readlines_FREAD)STRINGIFY(x):将宏拼接后的结果转换为字符串,用于TEST_LIST中的名称字段- 条件编译
#if defined(READ_IMPL):确保编译时必须指定有效的实现类型,避免无意义的编译错误
内容的提问来源于stack exchange,提问作者Madagascar
相关产品推荐
相关产品推荐

