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 packagesfind_package(GTest REQUIRED)
# Find all test source filesfile(GLOB TEST_SOURCES "*.cpp")
# Create Library to Test...
# For each test file, create a separate test executableforeach(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 targetall: build
build: @cmake -S . -B build @cmake --build build -- -j$(shell nproc)
# Run teststest: build @cd build && ctest --output-on-failure
# Run specific test by nametest-%: build @cd build && ctest -R $* --output-on-failure
# Run tests with verbose outputtest-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执行之后被调用。