如何实现一个轻量级 C++ 单元测试框架——MiniTest
在 C++ 项目开发中,单元测试 是保证代码质量的重要手段。尽管
Google Test
和Boost.Test
是流行的测试框架,但它们较为复杂,适用于大型项目。如果你想要一个轻量级、易于理解的 C++ 单元测试框架,本文将带你实现 MiniTest,一个仅需几个头文件即可完成的 C++ 单元测试框架。
📌 为什么需要自定义单元测试框架?
在 C++ 项目中,单元测试通常需要具备以下能力:
- 断言机制:检查某个条件是否成立,失败时给出错误提示。
- 测试管理:能够注册、存储和执行多个测试用例。
- 灵活运行:支持运行所有测试,也支持运行单个测试。
- 跨平台支持:能够在不同平台(Windows、Linux、Mac)上运行。
既然 gtest
和 Boost.Test
已经很强大,为什么还要自己实现一个框架呢?
- 轻量级:无需引入外部库,减少依赖。
- 更好理解:帮助你深入理解单元测试框架的实现原理。
- 定制化:可以根据需求自由扩展。
📌 MiniTest 框架的组成
MiniTest 框架由 三个主要组件 组成:
- 断言宏 (
TestAssert.hpp
):提供ASSERT_TRUE
,ASSERT_EQ
,ASSERT_THROW
等基本断言。 - 测试管理 (
TestFramework.hpp
):负责存储、注册和执行测试。 - 测试代码 (
main.cpp
):用户编写测试用例,并运行测试。
我们将在下面的部分详细介绍它们的作用,并给出代码实现。
📌 1. 断言宏:TestAssert.hpp
💡 作用
TestAssert.hpp
负责提供基本的断言机制,用于检查测试是否通过。例如:
ASSERT_TRUE(condition)
:如果condition
不是true
,测试失败。ASSERT_EQ(expected, actual)
:如果expected != actual
,测试失败。ASSERT_THROW(statement, exception_type)
:检查statement
是否抛出exception_type
。
📌 代码示例
#ifndef TEST_ASSERT_HPP
#define TEST_ASSERT_HPP
#include <iostream>
#include <stdexcept>
// 断言:检查是否为真
#define ASSERT_TRUE(condition) \
do { \
if (!(condition)) { \
throw std::runtime_error("Assertion failed: " #condition); \
} \
} while (0)
// 断言:检查是否为假
#define ASSERT_FALSE(condition) ASSERT_TRUE(!(condition))
// 断言:检查两个值是否相等
#define ASSERT_EQ(expected, actual) \
do { \
if ((expected) != (actual)) { \
throw std::runtime_error("Assertion failed: " #expected " != " #actual); \
} \
} while (0)
// 断言:检查两个值是否不相等
#define ASSERT_NE(expected, actual) ASSERT_TRUE((expected) != (actual))
// 断言:检查是否抛出指定的异常
#define ASSERT_THROW(statement, exception_type) \
do { \
bool bCaught = false; \
try { \
statement; \
} catch (const exception_type&) { \
bCaught = true; \
} catch (...) { \
throw std::runtime_error("Assertion failed: Unexpected exception caught."); \
} \
if (!bCaught) { \
throw std::runtime_error("Assertion failed: Exception of type " #exception_type " not thrown."); \
} \
} while (0)
#endif // TEST_ASSERT_HPP
📌 2. 测试管理:TestFramework.hpp
💡 作用
- 注册测试:存储所有测试函数。
- 执行测试:可以运行所有测试,也可以只运行指定测试。
- 输出结果:测试成功/失败的日志信息。
📌 代码示例
#ifndef TEST_FRAMEWORK_HPP
#define TEST_FRAMEWORK_HPP
#include <vector>
#include <functional>
#include <string>
#include <iostream>
// 单元测试框架
class TestFramework {
public:
// 注册测试
static void RegisterTest(const std::string& testName, std::function<void()> testFunc) {
GetTests().push_back({testName, testFunc});
}
// 运行所有测试
static void RunAllTests() {
int passed = 0;
int failed = 0;
for (const auto& test : GetTests()) {
try {
test.func();
std::cout << "[PASS] " << test.name << std::endl;
++passed;
} catch (const std::exception& ex) {
std::cerr << "[FAIL] " << test.name << " - " << ex.what() << std::endl;
++failed;
}
}
std::cout << "===================================" << std::endl;
std::cout << "Total: " << (passed + failed) << ", Passed: " << passed << ", Failed: " << failed << std::endl;
}
// 运行指定名称的测试
static void RunTestByName(const std::string& testName) {
for (const auto& test : GetTests()) {
if (test.name == testName) {
try {
test.func();
std::cout << "[PASS] " << test.name << std::endl;
} catch (const std::exception& ex) {
std::cerr << "[FAIL] " << test.name << " - " << ex.what() << std::endl;
}
return;
}
}
std::cerr << "[ERROR] Test '" << testName << "' not found." << std::endl;
}
private:
struct TestCase {
std::string name;
std::function<void()> func;
};
static std::vector<TestCase>& GetTests() {
static std::vector<TestCase> tests;
return tests;
}
};
// 定义 TEST 宏,自动注册测试
#define TEST(test_name) \
void test_name(); \
struct TestRegister_##test_name { \
TestRegister_##test_name() { \
TestFramework::RegisterTest(#test_name, test_name); \
} \
}; \
static TestRegister_##test_name g_register_##test_name; \
void test_name()
#endif // TEST_FRAMEWORK_HPP
📌 3. 编写测试代码
📌 main.cpp
:用于调用 RunAllTests()
或 RunTestByName()
运行测试。
📌 代码示例
#include <iostream>
#include "include/TestAssert.hpp"
#include "include/TestFramework.hpp"
// 数学加法测试
TEST(TestAddition) {
int a = 2;
int b = 3;
ASSERT_EQ(a + b, 5);
}
// 逻辑测试
TEST(TestBoolean) {
bool flag = true;
ASSERT_TRUE(flag);
}
// 失败示例(演示 `ASSERT_TRUE` 失败的情况)
TEST(TestFailure) {
bool flag = false;
ASSERT_TRUE(flag); // 这个测试会失败
}
// 异常测试
TEST(TestException) {
ASSERT_THROW(throw std::runtime_error("error"), std::runtime_error);
}
int main() {
TestFramework::RunAllTests(); // 运行所有测试
std::cout << "===================================" << std::endl;
TestFramework::RunTestByName("TestAddition"); // 只运行 TestAddition
return 0;
}
📌 运行 MiniTest
1. 编译
g++ -std=c++20 main.cpp -o MiniTest
2. 运行所有测试
./MiniTest
3. 运行单个测试
./MiniTest TestAddition
📌 通过 CLion
运行
文件组织结构
/project_root
│── /tests
│ │── test_example.cpp # 测试代码
│ │── test_math.cpp # 另一个测试代码
│── /include
│ │── TestFramework.hpp # 测试框架
│ │── TestAssert.hpp # 断言宏
│── /src
│ │── TestFramework.cpp # 测试框架的实现(如果有)
│── main.cpp # 运行所有测试的 main 函数
编写 CMakeLists.txt
cmake_minimum_required(VERSION 3.30)
project(MiniTest)
set(CMAKE_CXX_STANDARD 20)
# 确保 MSVC 使用 UTF-8
add_compile_options(/utf-8)
include_directories(include)
add_executable(MiniTest main.cpp)
运行
📌 总结
✅ 轻量级 C++ 单元测试框架
✅ 支持 ASSERT_TRUE
, ASSERT_EQ
, ASSERT_THROW
✅ 支持运行所有测试 or 运行特定测试
✅ 代码结构清晰,易于扩展
🚀 下一步实现
- 支持 Setup / Teardown
- 支持性能测试
- 支持测试分组