MiniTest: 轻量级 C++ 单元测试框架
·
简介
MiniTest 是一个轻量级 C++ 单元测试框架,提供 断言测试、参数化测试、分组测试、性能测试和 Mock(模拟对象),适用于小型项目的单元测试需求。
本框架的特点:
- 轻量级:无第三方依赖,适用于嵌入式、CLI 工具等。
- 易扩展:模块化设计,支持自定义测试。
- 支持参数化测试:使用
TEST_P运行多组输入测试。 - 支持性能测试:基准测试
BENCHMARK_FUNC测量函数执行时间。 - 支持 Mock:通过
Mock::SetReturn轻松模拟依赖。
项目结构
MiniTest/
│── include/ # 头文件目录
│ ├── TestAssert.hpp # 断言宏
│ ├── TestFramework.hpp # 基础测试框架
│ ├── TestParams.hpp # 参数化测试
│ ├── TestSuite.hpp # 分组测试
│ ├── TestBenchmark.hpp # 性能测试
│ ├── TestMock.hpp # Mock 模拟对象
│── tests/ # 测试用例
│── CMakeLists.txt # CMake 构建脚本
│── main.cpp # 主测试入口
│── README.md # 项目文档
环境配置
本项目使用 C++20,通过 CMake 构建。
cmake_minimum_required(VERSION 3.30)
project(MiniTest)
set(CMAKE_CXX_STANDARD 20)
include_directories(include)
add_executable(MiniTest main.cpp)
target_compile_options(MiniTest PRIVATE -finput-charset=UTF-8 -fexec-charset=UTF-8)
编译运行
mkdir build
cd build
cmake ..
make
1. 断言测试
示例代码
TEST(TestBoolean) {
ASSERT_TRUE(true);
}
TEST(TestFailure) {
ASSERT_TRUE(false); // 这个测试会失败
}
TEST(TestException) {
ASSERT_THROW(throw std::runtime_error("error"), std::runtime_error);
}
TEST(TestSubtraction) {
ASSERT_EQ(5 - 3, 2);
}
完整代码
/**
* ==================================================
* @file TestAssert.hpp
* @brief 断言宏定义,提供基本的单元测试断言功能
* @author mrDarker
* @date 2025/03/18
* @version 1.0
* @copyright Copyright (c) 2025 mrDarker. All Rights Reserved.
* ==================================================
*/
#ifndef TEST_ASSERT_HPP
#define TEST_ASSERT_HPP
#include <iostream>
#include <stdexcept>
// 断言失败时记录日志
#define ASSERT_FAIL(message) \
do { \
/**std::ostringstream oss; \
oss << "Assertion failed: " << message << " at " << __FILE__ << ":" << __LINE__; \
std::cerr << "[ASSERT FAIL] " << oss.str() << std::endl; \
TestLogger logger("AssertFailures"); \
logger.LogMessage(oss.str()); \
std::cout.flush(); \
std::cerr.flush(); \
throw std::runtime_error(oss.str()); \ **/ \
} while (0)
// 断言:检查是否为真
#define ASSERT_TRUE(condition) \
do { \
if (!(condition)) { \
ASSERT_FAIL(#condition " is false"); \
} \
} while (0)
// 断言:检查是否为假
#define ASSERT_FALSE(condition) \
do { \
if ((condition)) { \
ASSERT_FAIL(#condition " is true"); \
} \
} while (0)
// 断言:检查两个值是否相等
#define ASSERT_EQ(expected, actual) \
do { \
if ((expected) != (actual)) { \
std::ostringstream oss; \
oss << #expected " != " #actual " (" << expected << " != " << actual << ")"; \
ASSERT_FAIL(oss.str()); \
} \
} while (0)
// 断言:检查两个值是否不相等
#define ASSERT_NE(expected, actual) \
do { \
if ((expected) == (actual)) { \
std::ostringstream oss; \
oss << #expected " == " #actual " (" << expected << " == " << actual << ")"; \
ASSERT_FAIL(oss.str()); \
} \
} while (0)
// 断言:检查某个语句是否抛出指定的异常
#define ASSERT_THROW(statement, exception_type) \
do { \
bool caught = false; \
try { \
statement; \
} catch (const exception_type&) { \
caught = true; \
} catch (...) { \
ASSERT_FAIL("Unexpected exception type thrown"); \
} \
if (!caught) { \
ASSERT_FAIL("Expected exception of type " #exception_type " not thrown"); \
} \
} while (0)
#endif // TEST_ASSERT_HPP
支持的断言宏
| 宏 | 说明 |
|---|---|
ASSERT_TRUE(x) | 断言 x 为 true |
ASSERT_FALSE(x) | 断言 x 为 false |
ASSERT_EQ(a, b) | 断言 a == b |
ASSERT_NE(a, b) | 断言 a != b |
ASSERT_THROW(expr, exception_type) | 断言 expr 抛出 exception_type |
工作原理
ASSERT_*宏会检查表达式的结果。- 如果断言失败,会抛出
std::runtime_error,使测试失败。 TestFramework::RunAllTests()负责运行所有注册的测试。
2. 参数化测试
示例代码
std::vector<std::tuple<int, int, int>> additionParams = {
{1, 2, 3}, {4, 5, 9}, {3, 7, 10}, {6, -2, 4}, {8, 3, 12}
};
TEST_P(TestParamsAddition, additionParams, int a, int b, int expected) {
ASSERT_EQ(a + b, expected);
}
完整代码
/**
* ==================================================
* @file TestParams.hpp
* @brief 参数化测试框架,支持数据集驱动测试
* @author mrDarker
* @date 2025/03/18
* @version 1.1
* @copyright Copyright (c) 2025 mrDarker. All Rights Reserved.
* ==================================================
*/
#ifndef TEST_PARAMS_HPP
#define TEST_PARAMS_HPP
#include <vector>
#include <string>
#include <functional>
#include <iostream>
#include <sstream>
#include <tuple>
#include "TestLogger.hpp"
// 参数化测试的存储结构
class TestParams {
public:
// 注册参数化测试
template <typename Func, typename... Args>
static void RegisterParamTest(const std::string& name, Func func, const std::vector<std::tuple<Args...>>& params) {
for (const auto& paramSet : params) {
std::ostringstream oss;
oss << name << FormatParams(paramSet);
GetParamTests().push_back({oss.str(), [func, paramSet]() { std::apply(func, paramSet); }});
}
}
// 运行所有参数化测试
static void RunAllParamTests() {
TestLogger logger("ParamTests");
int passed = 0, failed = 0;
for (const auto&[name, func] : GetParamTests()) {
std::cout.flush();
std::cerr.flush();
std::cout << "[RUNNING] " << name << std::endl;
try {
func(); // 无参数调用
std::cout << "[PASS] " << name << std::endl;
logger.LogTestResult(name, true);
++passed;
} catch (const std::exception& ex) {
std::cerr << "[FAIL] " << name << " - " << ex.what() << std::endl;
logger.LogTestResult(name, false);
++failed;
}
}
// 打印测试统计信息
std::cout << "===========================================" << std::endl;
std::cout << "Total: " << (passed + failed) << ", Passed: " << passed << ", Failed: " << failed << std::endl;
std::cout << "===========================================" << std::endl;
std::cout.flush();
std::cerr.flush();
}
private:
struct ParamTestCase {
std::string name;
std::function<void()> func;
};
static std::vector<ParamTestCase>& GetParamTests() {
static std::vector<ParamTestCase> paramTests;
return paramTests;
}
// 格式化参数列表
template <typename Tuple, size_t... Index>
static std::string FormatTupleImpl(const Tuple& tuple, std::index_sequence<Index...>) {
std::ostringstream oss;
((oss << (Index == 0 ? "" : ", ") << std::get<Index>(tuple)), ...);
return "(" + oss.str() + ")";
}
template <typename... Args>
static std::string FormatParams(const std::tuple<Args...>& params) {
return FormatTupleImpl(params, std::index_sequence_for<Args...>{});
}
};
// 参数化测试宏
#define TEST_P(test_name, param_data, ...) \
void test_name(__VA_ARGS__); \
namespace { \
struct Register_##test_name { \
Register_##test_name() { \
static const auto _param_data = param_data; \
TestParams::RegisterParamTest(#test_name, test_name, _param_data); \
} \
}; \
static Register_##test_name g_register_##test_name; \
} \
void test_name(__VA_ARGS__)
#endif // TEST_PARAMS_HPP
工作原理
TEST_P宏会自动生成测试函数,并注册到TestParams::RegisterParamTest。std::tuple<>存储不同的测试数据。TestParams::RunAllParamTests()依次调用测试用例,并使用不同参数执行。
3. 分组测试
示例代码
TEST_SUITE(MathTests, TestSuiteAddition) {
ASSERT_EQ(2 + 3, 5);
}
TEST_SUITE_F(MySuiteTestFixture, MathTests, TestAddition) {
ASSERT_EQ(1 + 1, 2);
}
完整代码
/**
* ==================================================
* @file TestSuite.hpp
* @brief 测试分组管理,支持运行指定测试套件
* @author mrDarker
* @date 2025/03/18
* @version 1.1
* @copyright Copyright (c) 2025 mrDarker. All Rights Reserved.
* ==================================================
*/
#ifndef TEST_SUITE_HPP
#define TEST_SUITE_HPP
#include <vector>
#include <functional>
#include <unordered_map>
#include <iostream>
#include <ranges>
#include <thread>
#include "TestLogger.hpp"
class TestSuite {
public:
// 结构体: 测试用例
struct TestCase {
std::string name; // 测试名称
std::function<void()> func; // 测试执行函数
};
/**
* @brief 注册测试到指定的 Suite
* @param suite 测试套件名称
* @param test 测试名称
* @param func 测试函数
*/
static void RegisterTest(const std::string& suite, const std::string& test, const std::function<void()> &func) {
GetSuites()[suite].push_back({test, func});
}
/**
* @brief 打印所有已注册的测试套件
*/
static void ListSuites() {
std::cout << "[AVAILABLE TEST SUITES]" << std::endl;
for (const auto &suite: GetSuites() | std::views::keys) {
std::cout << "- " << suite << std::endl;
}
}
/**
* @brief 执行指定测试套件的 Setup
*/
static void SetupSuite(const std::string& suite) {
if (auto& setupFuncs = GetSetupFuncs(); setupFuncs.contains(suite)) setupFuncs[suite]();
}
/**
* @brief 执行指定测试套件的 Teardown
*/
static void TeardownSuite(const std::string& suite) {
if (auto& teardownFuncs = GetTeardownFuncs(); teardownFuncs.contains(suite)) teardownFuncs[suite]();
}
/**
* @brief 获取所有测试套件的 Setup 函数映射
* @return std::unordered_map<std::string, std::function<void()>>&
*/
static std::unordered_map<std::string, std::function<void()>>& GetSetupFuncs() {
static std::unordered_map<std::string, std::function<void()>> setupFuncs;
return setupFuncs;
}
/**
* @brief 获取所有测试套件的 Teardown 函数映射
* @return std::unordered_map<std::string, std::function<void()>>&
*/
static std::unordered_map<std::string, std::function<void()>>& GetTeardownFuncs() {
static std::unordered_map<std::string, std::function<void()>> teardownFuncs;
return teardownFuncs;
}
/**
* @brief 运行指定测试套件,可选过滤单个测试
* @param suite 测试套件名称
* @param testFilter 需要执行的具体测试(默认为空,执行整个套件)
*/
static void RunSuite(const std::string& suite, const std::string& testFilter = "") {
auto& suites = GetSuites();
if (!suites.contains(suite)) {
std::cerr << "[ERROR] Test suite '" << suite << "' not found.\n";
return;
}
auto& tests = suites[suite];
// 如果 testFilter 为空,执行整个 Suite
if (testFilter.empty()) {
std::cout << "[RUNNING SUITE] " << suite << std::endl;
RunTests(tests, suite);
return;
}
// 查找是否存在该 Test
auto it = std::ranges::find_if(tests, [&](const TestCase& test) {
return test.name == testFilter;
});
// 如果存在,执行该 Test
if (it != tests.end()) {
std::cout << "[RUNNING SINGLE TEST] " << suite << "::" << testFilter << std::endl;
RunTests({*it}, suite);
} else {
std::cerr << "[ERROR] Test '" << testFilter << "' not found in suite '" << suite << "'.\n";
}
}
/**
* @brief 运行测试列表
* @param tests 需要执行的测试列表
* @param suite 所属的测试套件名称
*/
static void RunTests(const std::vector<TestCase>& tests, const std::string& suite) {
TestLogger logger(suite);
int passed = 0, failed = 0;
// 执行 Setup
SetupSuite(suite);
for (const auto& [name, func] : tests) {
std::cout << "[RUNNING] " << name << std::endl;
try {
func();
std::cout << "[PASS] " << name << std::endl;
logger.LogTestResult(name, true);
++passed;
} catch (const std::exception& ex) {
std::cerr << "[FAIL] " << name << " - " << ex.what() << std::endl;
logger.LogTestResult(name, false);
++failed;
}
}
// 执行 Teardown
TeardownSuite(suite);
std::cout << "[SUITE] " << suite << " - Passed: " << passed << ", Failed: " << failed << std::endl;
}
/**
* @brief 运行所有测试套件
*/
static void RunAllSuites() {
std::vector<std::thread> threads;
for (const auto &key: GetSuites() | std::views::keys) {
threads.emplace_back([key]() { RunSuite(key); });
}
for (auto& t : threads) {
if (t.joinable()) t.join();
}
}
private:
/**
* @brief 获取所有已注册的测试套件
*/
static std::unordered_map<std::string, std::vector<TestCase>>& GetSuites() {
static std::unordered_map<std::string, std::vector<TestCase>> suites;
return suites;
}
};
// **注册 Setup**
#define TEST_SUITE_SETUP(suite_name, func) \
namespace { struct RegisterSetup_##suite_name { \
RegisterSetup_##suite_name() { TestSuite::GetSetupFuncs()[#suite_name] = func; } \
}; static RegisterSetup_##suite_name g_registerSetup_##suite_name; }
// **注册 Teardown**
#define TEST_SUITE_TEARDOWN(suite_name, func) \
namespace { struct RegisterTeardown_##suite_name { \
RegisterTeardown_##suite_name() { TestSuite::GetTeardownFuncs()[#suite_name] = func; } \
}; static RegisterTeardown_##suite_name g_registerTeardown_##suite_name; }
// **注册普通测试**
#define TEST_SUITE(suite_name, test_name) \
void test_name(); \
namespace { \
struct Register_##test_name { \
Register_##test_name() { TestSuite::RegisterTest(#suite_name, #test_name, test_name); } \
}; \
static Register_##test_name g_register_##test_name; \
} \
void test_name()
// **注册带 Fixture 的测试**
#define TEST_SUITE_F(fixture_name, suite_name, test_name) \
class test_name : public fixture_name { \
public: \
void Run(); \
}; \
namespace { \
struct Register_##test_name { \
Register_##test_name() { \
TestSuite::RegisterTest(#suite_name, #test_name, []() { \
test_name instance; \
instance.Run(); \
}); \
} \
}; \
static Register_##test_name g_register_##test_name; \
} \
void test_name::Run()
#endif // TEST_SUITE_HPP
工作原理
TEST_SUITE(suite, test)注册测试到TestSuite::RegisterTest。TEST_SUITE_F(fixture, suite, test)支持使用SetUp()和TearDown()。TestSuite::RunSuite("MathTests")运行整个测试套件。
4. 性能测试
示例代码
BENCHMARK_FUNC(MyTestFunction, 5);
void MyTestFunction() {
std::vector<int> data(100000);
std::ranges::generate(data.begin(), data.end(), rand);
std::ranges::sort(data.begin(), data.end());
}
完整代码
/**
* ==================================================
* @file TestBenchmark.hpp
* @brief 基准测试框架,支持多次运行、平均时间统计和参数化基准测试
* @author mrDarker
* @date 2025/03/18
* @version 1.2
* @copyright Copyright (c) 2025 mrDarker. All Rights Reserved.
* ==================================================
*/
#ifndef TEST_BENCHMARK_HPP
#define TEST_BENCHMARK_HPP
#include <chrono>
#include <functional>
#include <iostream>
#include <vector>
#include <unordered_map>
#include "TestLogger.hpp"
class TestBenchmark {
public:
// 普通基准测试(无参数)
static void RegisterBenchmark(const std::string& name, const std::function<void()>& func, const int iterations) {
GetBenchmarks()[name] = {func, iterations};
}
// 参数化基准测试(有参数)
template <typename Func, typename... Args>
static void RegisterBenchmark(const std::string& name, Func func, int iterations, Args... args) {
auto wrapper = [func, args...]() { func(args...); };
GetBenchmarks()[name] = {wrapper, iterations};
}
// 运行所有基准测试
static void RunAllBenchmarks() {
RunFilteredBenchmarks([](const std::string&) { return true; }, "AllBenchmarks");
}
// 运行指定的基准测试
static void RunBenchmark(const std::string& name) {
RunFilteredBenchmarks([name](const std::string& testName) {
return testName == name;
}, "Benchmark_" + name);
}
// 单独测试某个函数的平均执行时间
template <typename ReturnType, typename... Args>
static void BenchmarkFunction(const std::string& name, ReturnType (*func)(Args...), const int iterations, Args... args) {
std::cout << "[BENCHMARK] Running " << name << " for " << iterations << " iterations..." << std::endl;
std::vector<double> times;
for (int i = 0; i < iterations; ++i) {
std::cout.flush();
std::cerr.flush();
auto start = std::chrono::high_resolution_clock::now();
if constexpr (sizeof...(Args) > 0) {
func(args...); // 如果有参数,调用带参数的函数
} else {
func(); // 如果无参数,调用无参数的函数
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> duration = end - start;
times.push_back(duration.count());
}
double totalTime = 0;
for (const double t : times) totalTime += t;
const double avgTime = totalTime / iterations;
std::cout << "[BENCHMARK] " << name << " Avg Time: " << avgTime << " ms" << std::endl;
}
private:
struct BenchmarkCase {
std::function<void()> func;
int iterations{};
};
static std::unordered_map<std::string, BenchmarkCase>& GetBenchmarks() {
static std::unordered_map<std::string, BenchmarkCase> benchmarks;
return benchmarks;
}
// 通用性能测试执行
static void RunFilteredBenchmarks(const std::function<bool(const std::string&)>& filter, const std::string& logCategory) {
TestLogger logger(logCategory);
for (const auto& [fst, snd] : GetBenchmarks()) {
if (!filter(fst)) continue;
const auto& [func, iterations] = snd;
std::cout << "[BENCHMARK] Running " << fst << " for " << iterations << " iterations..." << std::endl;
std::vector<double> times;
for (int i = 0; i < iterations; ++i) {
std::cout.flush();
std::cerr.flush();
auto start = std::chrono::high_resolution_clock::now();
func();
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> duration = end - start;
times.push_back(duration.count());
}
double totalTime = 0;
for (const double t : times) totalTime += t;
const double avgTime = totalTime / iterations;
std::cout << "[BENCHMARK] " << fst << " Avg Time: " << avgTime << " ms" << std::endl;
logger.LogMessage("[BENCHMARK] " + fst + " Avg Time: " + std::to_string(avgTime) + " ms");
}
}
};
// 普通基准测试(无参数)
#define BENCHMARK(test_name, iterations) \
void test_name(); \
namespace { \
struct Register_##test_name { \
Register_##test_name() { \
TestBenchmark::RegisterBenchmark(#test_name, test_name, iterations); \
} \
}; \
static Register_##test_name g_register_##test_name; \
} \
void test_name()
// 指定函数基准测试
#define BENCHMARK_FUNC(func, iterations, ...) \
namespace { \
struct Register_##func { \
Register_##func() { \
TestBenchmark::BenchmarkFunction(#func, func, iterations, ##__VA_ARGS__); \
} \
}; \
static Register_##func g_register_##func; \
}
#endif // TEST_BENCHMARK_HPP
工作原理
BENCHMARK_FUNC注册MyTestFunction,并运行 5 次。TestBenchmark::BenchmarkFunction()计算平均执行时间。
5. Mock(模拟对象)
示例代码
MOCK_METHOD(int, GetRandomNumber, ());
TEST(TestMockExample) {
Mock::SetReturn(GetRandomNumber, 42);
ASSERT_EQ(GetRandomNumber(), 42);
}
完整代码
/**
* ==================================================
* @file TestMock.hpp
* @brief 模拟对象框架,支持 Mock 方法和返回值设置
* @author mrDarker
* @date 2025/03/18
* @version 1.0
* @copyright Copyright (c) 2025 mrDarker. All Rights Reserved.
* ==================================================
*/
#ifndef TEST_MOCK_HPP
#define TEST_MOCK_HPP
#include <unordered_map>
#include <functional>
#include <typeindex>
#include <map>
class Mock {
public:
// 设置 Mock 方法的返回值
template <typename ReturnType>
static void SetReturn(ReturnType (*func)(), ReturnType returnValue) {
auto wrapper = [returnValue]() -> ReturnType { return returnValue; };
GetMockFunctions<ReturnType>()[reinterpret_cast<void*>(func)] = wrapper;
}
// 调用 Mock 方法
template <typename ReturnType>
static ReturnType Invoke(ReturnType (*func)()) {
auto& mockMap = GetMockFunctions<ReturnType>();
auto it = mockMap.find(reinterpret_cast<void*>(func));
if (it != mockMap.end()) {
return it->second();
}
// std::cerr << "[MOCK ERROR] Function '" << typeid(func).name() << "' not mocked!\n";
return ReturnType(); // 默认返回 0 / 空值
}
// 清除 Mock
template <typename ReturnType>
static void Reset() {
GetMockFunctions<ReturnType>().clear();
}
// 清空所有 Mock 方法
static void ResetAll() {
mockStorage().clear();
}
private:
// 存储不同类型的 Mock 方法
template <typename ReturnType>
static std::unordered_map<void*, std::function<ReturnType()>>& GetMockFunctions() {
static std::unordered_map<void*, std::function<ReturnType()>> mockFunctions;
return mockFunctions;
}
static std::map<std::type_index, void*>& mockStorage() {
static std::map<std::type_index, void*> storage;
return storage;
}
};
// MOCK_METHOD 宏
#define MOCK_METHOD(returnType, functionName, params) \
returnType functionName params; \
namespace { \
struct RegisterMock_##functionName { \
RegisterMock_##functionName() { \
Mock::SetReturn(functionName, returnType()); \
} \
}; \
static RegisterMock_##functionName g_register_mock_##functionName; \
} \
returnType functionName params { return Mock::Invoke(functionName); }
#endif // TEST_MOCK_HPP
工作原理
MOCK_METHOD定义 Mock 方法。Mock::SetReturn(func, value)设置返回值。Mock::Reset()清除 Mock 方法。
6. 运行测试
int main() {
TestFramework::RunAllTests();
TestParams::RunAllParamTests();
TestBenchmark::RunAllBenchmarks();
TestSuite::RunAllSuites();
return 0;
}
总结
MiniTest 提供 断言、参数化、分组、性能测试和 Mock,适用于 C++ 小型项目单元测试。
✅ 轻量级,无第三方依赖
✅ 支持参数化测试
✅ 支持性能测试
✅ 支持 Mock
✅ 易扩展
这篇博客整理了完整的代码和实现原理。如果有任何改进建议,请告诉我!🚀
更多推荐
所有评论(0)