如何用TDD无重复测试带特化的可变参数模板代码?
解决测试代码重复的方案
针对你的问题,我们可以通过提取通用测试逻辑或者参数化测试用例的方式,同时覆盖两种调用方式的测试,又避免重复代码。下面提供两种实用方案:
方案一:提取辅助测试函数
将重复的测试逻辑封装成辅助函数,在每个测试场景中复用,同时测试参数列表和vector版本:
#include <numeric> #include <vector> #include <catch2/catch_test_macros.hpp> #include <type_traits> // 原函数实现保持不变 template<typename Type, typename = std::enable_if_t<std::is_integral_v<Type>>> std::size_t AddNumbers(std::vector<Type> Integers) { auto Sum = std::reduce(Integers.begin(), Integers.end()); return Sum; } template<typename... Type, typename = std::enable_if_t<std::conjunction_v<std::is_integral<Type>...>>> std::size_t AddNumbers(Type... Integers) { std::vector<std::size_t> IntegerSequence{(std::size_t)Integers...}; return AddNumbers(IntegerSequence); } // 通用测试辅助函数:同时验证两种调用方式 template<typename... Args> void VerifySum(std::size_t expected, Args... args) { // 测试参数列表调用 CHECK(AddNumbers(args...) == expected); // 测试vector调用:自动将参数转为对应类型的vector std::vector<std::common_type_t<Args...>> input_vec{args...}; CHECK(AddNumbers(input_vec) == expected); } // 无参数情况的单独测试函数 void VerifyEmptySum() { CHECK(AddNumbers() == 0); CHECK(AddNumbers(std::vector<int>{}) == 0); } TEST_CASE("Sum Test") { SECTION("When passed no numbers, returns 0") { VerifyEmptySum(); } SECTION("When passed 1 number, returns the number") { VerifySum(1, 1); } SECTION("When passed 2 numbers, returns the sum of numbers") { VerifySum(3, 1, 2); } // 可快速扩展更多测试场景 SECTION("When passed multiple positive integers, returns total sum") { VerifySum(15, 1, 2, 3, 4, 5); } }
方案优势
- 逻辑清晰:每个测试场景对应一个SECTION,可读性强
- 复用性高:新增测试场景只需调用
VerifySum,无需重复编写两次CHECK - 维护方便:修改测试逻辑只需调整辅助函数,无需逐个修改SECTION
方案二:参数化测试用例
将所有测试输入和预期结果整理成测试用例集合,遍历集合完成两种调用方式的测试,适合大量测试用例的场景:
#include <numeric> #include <vector> #include <catch2/catch_test_macros.hpp> #include <type_traits> #include <tuple> // 原函数实现保持不变 template<typename Type, typename = std::enable_if_t<std::is_integral_v<Type>>> std::size_t AddNumbers(std::vector<Type> Integers) { auto Sum = std::reduce(Integers.begin(), Integers.end()); return Sum; } template<typename... Type, typename = std::enable_if_t<std::conjunction_v<std::is_integral<Type>...>>> std::size_t AddNumbers(Type... Integers) { std::vector<std::size_t> IntegerSequence{(std::size_t)Integers...}; return AddNumbers(IntegerSequence); } TEST_CASE("Sum Test") { // 定义测试用例结构:输入序列、预期结果 struct TestCase { std::vector<int> inputs; std::size_t expected; }; // 所有测试用例集合 const std::vector<TestCase> test_cases = { {{}, 0}, // 空输入 {{1}, 1}, // 单个数字 {{1, 2}, 3}, // 两个数字 {{2, 4, 6}, 12}, // 多个数字 {{10, 20, 30}, 60} }; for (const auto& tc : test_cases) { // 测试vector版本 CHECK(AddNumbers(tc.inputs) == tc.expected); // 测试参数列表版本:利用std::apply将vector转为参数包 if (tc.inputs.empty()) { CHECK(AddNumbers() == tc.expected); } else { std::apply([&tc](auto... args) { CHECK(AddNumbers(args...) == tc.expected); }, tc.inputs); } } }
方案优势
- 集中管理:所有测试用例统一维护,新增/修改只需调整集合
- 减少冗余:无需为每个场景重复编写测试逻辑
- 扩展性强:批量添加测试用例非常高效
额外提示
原函数实现中,参数列表版本将输入转为std::vector<std::size_t>后调用vector版本,这会导致输入的整数类型被强制转换为size_t。如果需要保留原类型的语义,可以修改参数列表版本的实现:
template<typename... Type, typename = std::enable_if_t<std::conjunction_v<std::is_integral<Type>...>>> std::size_t AddNumbers(Type... Integers) { using CommonType = std::common_type_t<Type...>; std::vector<CommonType> IntegerSequence{static_cast<CommonType>(Integers)...}; return AddNumbers(IntegerSequence); }
内容的提问来源于stack exchange,提问作者KulaGGin
相关产品推荐
相关产品推荐

