前言
单元测试是软件开发过程中的重要部分,它帮助我们确保每个组件按预期工作。C++ 中进行单元测试的一个流行选择是 Catch2 测试框架,结合 CMake 构建系统,可以创建一个强大且灵活的测试环境。在这篇博客中,将演示使用 Catch2 集成到 CMake 的 CTest 工具中进行单元测试。
环境准备
- CMake:v3.28
- Catch 2:v3.6.0
假设目录结构如下:
1
2
3
4
5
6
7
| MyProject/
|-- CMakeLists.txt
|-- src/
|-- main.cpp
|-- test/
|-- CMakeLists.txt
|-- test.cpp
|
CMake 配置
根目录 CMakeLists.txt
:
1
2
3
4
5
6
7
8
9
10
| # CMakeLists.txt
cmake_minimum_required(VERSION 3.28)
...
option(ENABLE_TESTS "Build the test" ON)
...
if (ENABLE_TESTS)
# 启用测试
enable_testing()
add_subdirectory(test)
endif ()
|
使用 enable_testing()
启用 CTest 测试。
test
目录下的 CMakeLists.txt
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # test/CMakeLists.txt
include(FetchContent)
FetchContent_Declare(
Catch2
GIT_REPOSITORY https://github.com/catchorg/Catch2.git
GIT_TAG v3.6.0
)
FetchContent_MakeAvailable(Catch2)
add_executable(SimZipTest test.cpp)
target_link_libraries(SimZipTest PRIVATE SimZip Catch2::Catch2WithMain)
add_test(NAME SimZipTest
COMMAND $<TARGET_FILE:SimZipTest> --success
)
|
这里使用了 CMake 的 FetchContent
方式集成 Catch2,并指定了 Git tag 为 v3.6.0
,然后使用 target_link_libraries()
将需要链接的库和 Catch 的库链接上。
Catch 2 有两种方式:Catch2::Catch2
和 Catch2::Catch2WithMain
。如果不需要自定义 main 函数,则使用后者。如果需要自定义 main,则应该只链接 Catch2::Catch2
。
之后便是 add_test()
命令添加测试。
注意,这里使用了 --success
选项传递给单元测试的可执行文件,用于显示 Catch 2的输出。
编写测试代码
在 test
目录下,编写 test.cpp
代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| // test.cpp
#include "../SimZip.h"
#include <catch2/catch_test_macros.hpp>
#include <fstream>
#include <filesystem>
void generateData()
{
std::ofstream file("data.txt");
if (file.is_open()) {
for (auto i = 0; i < 10; i++) { file << "this is data for test."; }
file.close();
}
}
TEST_CASE("create zip", "[create_zip]")
{
generateData();
SimZip zip("test.zip", SimZip::OpenMode::Create);
REQUIRE(zip.add("data.txt") == true);
REQUIRE(zip.add("data.txt", "folder/rename.txt") == true);
REQUIRE(zip.add("empty.txt") == false);
zip.save();
}
TEST_CASE("extract zip", "[extract_zip]")
{
SimZip zip("test.zip", SimZip::OpenMode::Read);
SECTION("Extract single file from zip")
{
zip.extract("data.txt", "output/");
REQUIRE(fs::exists("output/data.txt"));
}
SECTION("Extract all files from zip")
{ zip.extractall("output/");
std::vector<std::string> expected_files = {"data.txt", "folder/rename.txt"};
for (const auto& file: expected_files) { REQUIRE(fs::exists("output/" + file)); }
}
}
|
在 Catch2 测试框架中,TEST_CASE
是一个宏,用于定义一个测试用例。这个宏接受几个参数,其中最常见的是测试用例的名称和可选的标签。
1
2
3
| TEST_CASE("test name", "[tags]") {
// 测试代码
}
|
- 测试用例名称:第一个参数是测试用例的名称,它是一个描述性的字符串,表明测试用例的目的或测试的行为。在 Catch2 中运行测试时,这个名称会被用来识别和过滤特定的测试。
- 标签:第二个参数是可选的,它允许你为测试用例添加标签。标签通常用方括号
[]
包围,并可以用逗号 ,
分隔,从而为测试用例分配多个标签。标签可以用来对测试进行分类,或者在运行测试时进行过滤。
SECTION
是一个宏,用于将一个测试案例(TEST_CASE
)划分为多个独立的部分,每个部分都可以单独运行或独立地进行断言检查。这允许你以模块化的方式编写测试,并且可以针对相同的测试设置运行多个不同的测试场景。
运行测试
使用命令:
1
2
3
| cmake -B build
cmake --build build
ctest --test-dir build/
|
然后就可以看到测试结果:
1
2
3
4
5
6
7
8
| $ ctest
Test project /mnt/f/Code/CppProjects/SimZip/build
Start 1: SimZipTest
1/1 Test #1: SimZipTest ....................... Passed 0.06 sec
100% tests passed, 0 tests failed out of 1
Total Test time (real) = 0.10 sec
|
对 CTest 添加 -V
参数,可以看到详细信息
1
| ctest --test-dir build/ -V
|
如果你使用的是 CLion,那么可以很方便地进行测试,选择 CTest 然后运行,即可执行测试。
测试结果:
总结
CMake 和 Catch2 的结合提供了一个简单而强大的单元测试解决方案,它可以帮助你确保代码的质量。通过遵循上述步骤,你可以轻松地在你的 C++ 项目中集成测试,并保持代码的可维护性和健壮性。