在 C++ 项目开发中,单元测试 是保证代码质量的重要手段。尽管
Google Test
和Boost.Test
是流行的测试框架,但它们较为复杂,适用于大型项目。如果你想要一个轻量级、易于理解的 C++ 单元测试框架,本文将带你实现 MiniTest,一个仅需几个头文件即可完成的 C++ 单元测试框架。
在 C++ 项目中,单元测试通常需要具备以下能力:
既然 gtest
和 Boost.Test
已经很强大,为什么还要自己实现一个框架呢?
MiniTest 框架由 三个主要组件 组成:
TestAssert.hpp
):提供 ASSERT_TRUE
, ASSERT_EQ
, ASSERT_THROW
等基本断言。TestFramework.hpp
):负责存储、注册和执行测试。main.cpp
):用户编写测试用例,并运行测试。我们将在下面的部分详细介绍它们的作用,并给出代码实现。
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
#include
// 断言:检查是否为真
#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
TestFramework.hpp
#ifndef TEST_FRAMEWORK_HPP
#define TEST_FRAMEWORK_HPP
#include
#include
#include
#include
// 单元测试框架
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
main.cpp
:用于调用 RunAllTests()
或 RunTestByName()
运行测试。
#include
#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;
}
g++ -std=c++20 main.cpp -o MiniTest
./MiniTest
./MiniTest TestAddition
CLion
运行/project_root
│── /tests
│ │── test_example.cpp # 测试代码
│ │── test_math.cpp # 另一个测试代码
│── /include
│ │── TestFramework.hpp # 测试框架
│ │── TestAssert.hpp # 断言宏
│── /src
│ │── TestFramework.cpp # 测试框架的实现(如果有)
│── main.cpp # 运行所有测试的 main 函数
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 运行特定测试
✅ 代码结构清晰,易于扩展
下一步实现