onqtam/doctest项目中的参数化测试详解
doctest 项目地址: https://gitcode.com/gh_mirrors/doc/doctest
概述
在单元测试中,参数化测试是一种强大的技术,它允许开发者使用不同的输入数据或类型来运行相同的测试逻辑。onqtam/doctest测试框架提供了两种参数化测试的方式:基于值的参数化测试和基于类型的模板化测试。
基于值的参数化测试
目前doctest尚未提供官方的值参数化支持,但开发者可以通过以下两种方式实现类似功能。
方法一:使用辅助函数循环测试
void doChecks(int data) {
// 使用data进行断言
}
TEST_CASE("测试名称") {
std::vector<int> data {1, 2, 3, 4, 5, 6};
for(auto& i : data) {
CAPTURE(i); // 记录当前输入数据
doChecks(i);
}
}
缺点分析:
- 当遇到异常或REQUIRE断言失败时,整个测试用例会终止,剩余数据不会被测试
- 需要手动使用CAPTURE或INFO记录测试数据
- 需要自行编写数据生成代码,增加了样板代码量
方法二:使用SUBCASE实现参数化
TEST_CASE("测试名称") {
int data;
SUBCASE("") { data = 1; }
SUBCASE("") { data = 2; }
CAPTURE(data);
// 使用data进行断言
}
缺点分析:
- 扩展性差,不适合大量测试数据
- 同样需要手动记录测试数据
改进方案:封装宏实现参数化
我们可以封装一个C++14的宏来简化操作:
#define DOCTEST_VALUE_PARAMETERIZED_DATA(data, data_container) \
static size_t _doctest_subcase_idx = 0; \
std::for_each(data_container.begin(), data_container.end(), [&](const auto& in) { \
DOCTEST_SUBCASE((std::string(#data_container "[") + \
std::to_string(_doctest_subcase_idx++) + "]").c_str()) { data = in; } \
}); \
_doctest_subcase_idx = 0
使用示例:
TEST_CASE("测试名称") {
int data;
std::list<int> data_container = {1, 2, 3, 4};
DOCTEST_VALUE_PARAMETERIZED_DATA(data, data_container);
printf("%d\n", data);
}
注意事项:
- 该宏不能与其他SUBCASE在同一代码块层级使用
- 只能在子用例中使用
- 数据容器必须是可迭代的(如std::vector、std::list等)
基于类型的模板化测试
当需要对多种类型实现相同的测试逻辑时,模板化测试非常有用。
直接模板化测试
TEST_CASE_TEMPLATE("有符号整数测试", T, char, short, int, long long int) {
T var = T();
--var;
CHECK(var == -1);
}
定义后调用的模板化测试
TEST_CASE_TEMPLATE_DEFINE("有符号整数测试", T, test_id) {
T var = T();
--var;
CHECK(var == -1);
}
TEST_CASE_TEMPLATE_INVOKE(test_id, char, short, int, long long int);
TEST_CASE_TEMPLATE_APPLY(test_id, std::tuple<float, double>);
测试命名规则: 测试用例名称会附加类型信息,如"有符号整数测试 "
类型字符串化处理
基本类型已内置字符串化支持,自定义类型需要使用TYPE_TO_STRING宏:
TYPE_TO_STRING(std::vector<int>);
注意事项:
- 类型不会自动去重,同一类型可能被多次实例化
- 不是所有类型都需要字符串化,默认会显示为"<>"
- 如需多类型参数化,可封装为类型对:
template <typename first, typename second>
struct TypePair {
typedef first A;
typedef second B;
};
#define pairs \
TypePair<int, char>, \
TypePair<char, int>
TEST_CASE_TEMPLATE("多类型测试", T, pairs) {
typedef typename T::A T1;
typedef typename T::B T2;
// 使用T1和T2类型
}
最佳实践建议
- 对于简单的值参数化测试,优先考虑辅助函数方式
- 当需要确保所有输入都被测试时,使用SUBCASE或封装宏的方式
- 对于接口或概念验证,使用模板化测试确保所有实现类型符合要求
- 在头文件中为常用自定义类型定义TYPE_TO_STRING,避免重复定义
- 考虑测试性能,避免过度参数化导致测试套件膨胀
通过合理使用参数化测试技术,可以显著提高测试代码的复用率,减少重复代码,同时确保测试覆盖全面。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考