
在SparkPost,我们投入大量时间和精力来测试我们的代码。我们的平台使用C语言编写,最近我研究了与一个名为“CppUTest”的单元测试框架的集成,该框架为C/C++提供了xUnit风格的测试。这个框架稳健、功能丰富,并且在积极开发中,是一个很好的选择。它还提供了C集成层,使其能够轻松与我们的平台C代码配合使用,尽管这个框架的大部分内容是C++。本教程介绍如何在自己的项目中开始使用CppUTest。
在 SparkPost,我们投入了大量时间和精力来测试我们的代码。我们的平台是用 C 语言编写的,最近我研究了如何与一个名为 “CppUTest” 的单元测试框架集成,该框架为 C/C++ 提供 xUnit 风格的测试。这个框架功能强大、功能丰富,并且在积极开发中,因此是一个很好的选择。它还提供了一个 C 集成层,使得即使框架的大部分是 C++,也能轻松与我们的平台 C 代码一起使用。本教程涵盖了如何在您的项目中开始使用 CppUTest。
奠定基础
Project Makefile
项目的 makefile 将位于项目根目录的 ‘src’ 和 ‘t’ 目录的同一级别。它应如下所示:
# Makefile SRC_DIR=./src CODE_DIR=$(SRC_DIR)/code OUT=example TEST_DIR=t test: make -C $(TEST_DIR) test_clean: make -C $(TEST_DIR) clean code.o: gcc -c -I$(CODE_DIR) $(CODE_DIR)/code.cpp -o $(CODE_DIR)/code.o main: code.o gcc -I$(CODE_DIR) $(CODE_DIR)/code.o $(SRC_DIR)/main.cpp -o $(OUT) all: test main clean: test_clean rm $(SRC_DIR)/*.o $(CODE_DIR)/*.o $(OUT)
注意,这里为测试目标使用了‘make -C’——这意味着它将在测试目录中再次调用‘make’。
在这一点上,我们可以使用 makefile 编译‘src’代码,并验证其工作:
[]$ make main gcc -c -I./src/code ./src/code/code.cpp -o ./src/code/code.o gcc -I./src/code ./src/code/code.o ./src/main.cpp -o example []$ ./example 你好,世界!
Tests Makefile
对于测试,由于我们需要正确加载并与CppUTest库集成,因此事情稍微复杂一些。
CppUTest存储库提供了一个名为“MakefileWorker.mk”的文件。它提供了许多功能,使得使用CppUTest进行构建变得简单。此文件位于git存储库的“build”目录下。在本教程中,我们假设它已经被复制到‘t/’目录。可以如下使用:
# 我们不想使用相对路径,因此我们设置这些变量 PROJECT_DIR=/path/to/project SRC_DIR=$(PROJECT_DIR)/src TEST_DIR=$(PROJECT_DIR)/t # 指定源代码和包含位于何处 INCLUDE_DIRS=$(SRC_DIR)/code SRC_DIRS=$(SRC_DIR)/code # 指定位于何处的测试代码 TEST_SRC_DIRS = $(TEST_DIR) # 测试二进制文件的命名 TEST_TARGET=example # CppUTest库的位置 CPPUTEST_HOME=/usr/local # 使用此处定义的变量运行MakefileWorker.mk include MakefileWorker.mk
请注意,CPPUTEST_HOME必须设置为CppUTest安装的位置。如果您安装了发行版包,这通常位于linux/mac系统的/usr/local下。如果您自己查看了存储库,则是您签出的地方。
所有这些选项都在MakefileWorker.mk中有记录。
MakefileWorker.mk还增加了一些Makefile目标,包括以下内容:
all – 构建Makefile指示的测试
clean – 删除为测试生成的所有对象和gcov文件
realclean – 删除整个目录树中的任何对象或gcov文件
flags – 列出用于编译测试的所有配置标志
debug – 列出所有源文件、对象、依赖项和‘要清理的内容’
代码覆盖率
单元测试如果没有覆盖率报告就不完整。对于使用gcc的项目,常用的工具是gcov,它是gcc标准工具套件的一部分。Cpputest与gcov集成简单,您只需在makefile中添加这一行:
CPPUTEST_USE_GCOV=Y
接下来,我们需要确保这个仓库中的filterGcov.sh脚本位于‘/scripts/filterGcov.sh’,相对于您设置的‘CPPUTEST_HOME’。它还需要具备执行权限。
在示例Makefile中,它将被部署到‘/usr/local/scripts/filterGcov.sh’。如果您从仓库签出运行CppUTest,所有功能应无需修改即可工作。
准备就绪后,您可以简单地运行‘make gcov’,分析将为您生成。在我们的例子中,我们需要‘make -B’以启用gcov重建目标文件:
[]$ make -B gcov < 编译输出 > 用于 d in /Users/ykuperman/code/blogpost/qa/src/code ; do \ FILES=`ls $d/*.c $d/*.cc $d/*.cpp 2> /dev/null` ; \ gcov --object-directory objs/$d $FILES >> gcov_output.txt 2>>gcov_error.txt ; \ done for f in ; do \ gcov --object-directory objs/$f $f >> gcov_output.txt 2>>gcov_error.txt ; \ done /usr/local/scripts/filterGcov.sh gcov_output.txt gcov_error.txt gcov_report.txt example.txt cat gcov_report.txt 100.00% /Users/ykuperman/code/blogpost/qa/src/code/code.cpp mkdir -p gcov mv *.gcov gcov mv gcov_* gcov 请查看gcov目录以获取详细信息
这将输出多个文件到一个新的‘gcov’目录。这些文件是:
code.cpp.gcov – 被测试代码的实际‘gcov’文件
gcov_error.txt – 错误报告(在我们的例子中,它应该是空的)
gcov_output.txt – 实际运行的gcov命令的输出
gcov_report.txt – 每个被测试文件的覆盖率摘要
gcov_report.txt.html – gcov_report的html版本
Cpputest Memory Leak 检测
Cpputest 通过重新定义标准的“malloc/free”系列函数来使用其自身的包装器,从而自动检测内存泄漏。这使它能快速捕捉泄漏并在每次测试执行时报告。这在 MakefileWorker.mk 中默认启用,因此在前面的步骤中已经是启用状态。
为了说明这一点,让我们在 test_func() 中泄露一些内存!
回到 code.c,我们在函数中添加一个 malloc(),如下所示:
int test_func() { malloc(1); return 1; }
现在,重新编译后,会产生以下错误:
test.cpp:9: error: Failure in TEST(AwesomeExamples, FirstExample) 发现内存泄漏。分配编号(4)泄漏大小:1 分配于:./code.c 和行:6。类型:“malloc” 内存:<0x7fc9e94056d0> 内容:0000: 00 |.| 总泄漏次数:1
这显示了哪个测试导致泄漏、源码中泄漏发生的位置以及泄漏内存中的内容。非常有帮助!
此功能有几个注意事项:
Cpputest 使用预处理器宏动态重新定义所有对标准内存管理函数的调用。 这意味着它只对正在测试的源代码中的调用有效,因为那些代码是使用 CppUTest 的覆盖代码进行编译的。 链接库中的泄漏将不会被捕获。
有时分配给整个进程生命周期的内存并不打算被释放。如果您正在测试一个具有此行为的模块,这可能会产生很多垃圾错误。 要禁用泄漏检测,您可以这样做:
CPPUTEST_USE_MEM_LEAK_DETECTION=N
感兴趣?
这只是冰山一角,这个工具包含的所有功能远不止于此。除了这里讨论的基础知识之外,它还具有一个mock框架、一个直接的C集成层和一个插件框架,仅举几个重要的功能。这个repo还包含一个完整的帮助手册目录,可以帮助自动化处理框架中的一些日常工作部分。
我希望这里的信息能帮助您使用这个出色的工具提高您的C/C++代码质量!