Warning
本文不是教程,只是一个模板,方便本人复制粘贴而已
部分内容参考自博客 GTest / GMock 单元测试实践手册,详细的可以进博客学习
项目架构
假定项目的结构为:
.
├── build
├── CMakeLists.txt
├── Makefile
├── README.md
├── scripts
├── src
│ └── main.cpp
└── tests
├── CMakeLists.txt
└── tree.cpp我们在 tests 中写单元测试,其中,一个 cpp 文件是一个大的单元测试
CMakeLists
我们在根目录的 CMakeLists.txt 的最后写上:
...
enable_testing()
add_subdirectory(tests)然后,在 tests/CMakeLists.txt 中写上:
# Find required packages
find_package(GTest REQUIRED)
# Find all test source files
file(GLOB TEST_SOURCES "*.cpp")
# Create Library to Test
...
# For each test file, create a separate test executable
foreach(TEST_SOURCE ${TEST_SOURCES})
get_filename_component(TEST_NAME ${TEST_SOURCE} NAME_WE)
add_executable(${TEST_NAME}_test ${TEST_SOURCE})
target_link_libraries(${TEST_NAME}_test
${CUSTOM_LIBS}
)
# Add to CTest
add_test(NAME ${TEST_NAME} COMMAND ${TEST_NAME}_test)
endforeach()然后我们在根目录的 Makefile 下创建一个 test 目标:
.PHONY: all build test
# Default target
all: build
build:
@cmake -S . -B build
@cmake --build build -- -j$(shell nproc)
# Run tests
test: build
@cd build && ctest --output-on-failure
# Run specific test by name
test-%: build
@cd build && ctest -R $* --output-on-failure
# Run tests with verbose output
test-verbose: build
@cd build && ctest -V
这样,我们写完测试后即可通过运行
make test做单元测试
单元测试模板
这里我们通过 TestFixture 的方式来写单元测试,例如现在我有一个二叉树 PartitionTree,那么我们的单元测试如下:
#include "partition_tree.hpp"
#include <gtest/gtest.h>
#include <sys/types.h>
class PartitionTreeTest : public ::testing::Test {
public:
PartitionTree tree;
std::string debug_message() {
std::stringstream ss;
return tree.to_string();
}
protected:
void SetUp() override { tree.init(n_threads); }
void TearDown() override { tree.clear(); }
std::vector<int> assumptions = {1, 2, 3, 4, 5, 6, 7};
int depth = 3;
int n_threads = 8;
};
TEST_F(PartitionTreeTest, BasicInitializationBinMode) {
tree.init_with_binary(assumptions, depth, n_threads);
EXPECT_TRUE(tree.m_root != nullptr);
for (int i = 0; i < n_threads; i++) {
EXPECT_TRUE((int)tree.collect_pre_decision(i).size() == depth);
}
EXPECT_TRUE(
reinterpret_cast<Node *>(tree.m_wid_to_addr[0])->m_parent->m_split_lit ==
3);
}
TEST_F(PartitionTreeTest, BasicInitializationScaMode) {
tree.init_with_scattering(assumptions, 2);
auto w0_pd = tree.collect_pre_decision(0);
auto w1_pd = tree.collect_pre_decision(1);
EXPECT_TRUE(w0_pd.size() == 1 && w1_pd.size() == 1);
EXPECT_TRUE(w0_pd[0] == 1 && w1_pd[0] == -1) << debug_message();
}
TEST_F(PartitionTreeTest, BasicUpdateInBinMode) {
tree.init_with_binary(assumptions, depth, n_threads);
u_int64_t w1_addr = tree.m_wid_to_addr[1];
tree.update(0, 1, 9);
u_int64_t w1_addr_new = tree.m_wid_to_addr[1];
EXPECT_TRUE(w1_addr != w1_addr_new);
auto w0_pd = tree.collect_pre_decision(0);
auto w1_pd = tree.collect_pre_decision(1);
EXPECT_TRUE(w0_pd.size() == 4);
EXPECT_TRUE(w1_pd.size() == 4);
EXPECT_TRUE(w0_pd.back() == -9);
EXPECT_TRUE(w1_pd.back() == 9);
}
TEST_F(PartitionTreeTest, BasicUpdateInScaMode) {
tree.init_with_scattering(assumptions, n_threads);
u_int64_t w1_addr = tree.m_wid_to_addr[1];
tree.update(0, 1, 9);
u_int64_t w1_addr_new = tree.m_wid_to_addr[1];
EXPECT_TRUE(w1_addr != w1_addr_new) << debug_message();
auto w0_pd = tree.collect_pre_decision(0);
auto w1_pd = tree.collect_pre_decision(1);
EXPECT_TRUE(w0_pd.size() == 2);
EXPECT_TRUE(w1_pd.size() == 2);
EXPECT_TRUE(w0_pd.back() == -9);
EXPECT_TRUE(w1_pd.back() == 9);
}
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
这个文件是一个大测试点,其中,每个小测试点 Test Case 通过 TEST_F(TestFixure, TestCaseName) 实现,我们这么写的原因是为了可以共享一些测试变量,也可以少写一些代码(
Tip
继承自
Test的类,其SetUp函数会在每个Test Case执行之前被调用,而TearDown函数则会在每个Test Case执行之后被调用。