Featured image of post 使用CMake和Catch2进行单元测试

使用CMake和Catch2进行单元测试

使用 CMake 和 Catch2 进行 C++ 项目的单元测试。

前言

单元测试是软件开发过程中的重要部分,它帮助我们确保每个组件按预期工作。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::Catch2Catch2::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]") { 
// 测试代码 
}
  1. 测试用例名称:第一个参数是测试用例的名称,它是一个描述性的字符串,表明测试用例的目的或测试的行为。在 Catch2 中运行测试时,这个名称会被用来识别和过滤特定的测试。
  2. 标签:第二个参数是可选的,它允许你为测试用例添加标签。标签通常用方括号 [] 包围,并可以用逗号 , 分隔,从而为测试用例分配多个标签。标签可以用来对测试进行分类,或者在运行测试时进行过滤。

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 然后运行,即可执行测试。

测试结果:

CLion CTest运行截图

总结

CMake 和 Catch2 的结合提供了一个简单而强大的单元测试解决方案,它可以帮助你确保代码的质量。通过遵循上述步骤,你可以轻松地在你的 C++ 项目中集成测试,并保持代码的可维护性和健壮性。