大家好,今天给各位分享C++ 单元测试工具:探索 Catch 的强大功能的一些知识,其中也会对进行解释,文章篇幅可能偏长,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在就马上开始吧!
那么我们应该使用哪一个呢?如果你在Google 中搜索:最好的C++ 单元测试框架。前两个是,一个是stackoverflow 问答,另一个是reddit 问答。这两个问题都指向同一个单元测试框架:Catch。
为什么使用 Catch
Catch的官方文档中有一篇文章:为什么我们还需要另一个C++测试框架?如果您有兴趣,可以看一下。对我来说,它最吸引人的地方是:
几乎不需要任何配置。它是一个单头文件测试框架。它根本不需要任何东西。
C++ 的单元测试工具 —— Catch
发表于2016-08-22|阅读次数: 2356
如果你平时使用Java语言进行开发,当你听到单元测试工具这个词时,你可能会立即想到JUnit。作为一名C++ 软件工程师,当我第一次计划对我的程序进行单元测试时,我的第一个想法是:有这样的工具吗?搜索了一段时间后,我的回答是:我应该使用哪一个?
我在学校的时候很少听说过C++单元测试工具,所以我一直以为这样的工具不存在。后来慢慢发现我们的选择比你想象的要多:Catch、Boost.Test、UnitTest++、lest、bandit、igloo、xUnit++、CppTest、CppUnit、CxxTest、cpputest、googletest、cute。
那么我们应该使用哪一个呢?如果你在Google 中搜索:最好的C++ 单元测试框架。前两个是,一个是stackoverflow 问答,另一个是reddit 问答。这两个问题都指向同一个单元测试框架:Catch。
为什么使用 Catch
Catch的官方文档中有一篇文章:为什么我们还需要另一个C++测试框架?如果您有兴趣,可以看一下。对我来说,它最吸引人的地方是:
几乎不需要任何配置。它是一个单头文件测试框架。它根本不需要任何额外的配置。语法非常简单明了。用它编写的测试代码就像自然语言一样容易理解。
如何使用它
Catch 是一个单一头文件库,您只需#include“catch.hpp”即可。然后你可以像这样编写测试代码:
|
SCENARIO( "矢量可以调整大小和调整大小", "[矢量]" ) {
GIVEN( "带有一些项目的向量" ) {
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
WHEN( "尺寸增加" ) {
v.调整大小(10);
THEN( "大小和容量变化" ) {
要求( v.size()==10 );
要求( v.capacity()=10 );
}
}
WHEN( "尺寸减小" ) {
v.调整大小(0);
THEN( "大小改变但容量不变" ) {
要求( v.size()==0 );
要求( v.capacity()=5 );
}
}
WHEN( "保留更多容量" ) {
v. 保留(10);
THEN( "容量改变但大小不变" ) {
要求( v.size()==5 );
要求( v.capacity()=10 );
}
}
WHEN( "保留较少容量" ) {
v.reserve(0);
THEN( "大小和容量都没有改变" ) {
要求( v.size()==5 );
要求( v.capacity()=5 );
}
}
}
}
|
这是几乎可读的代码,不需要解释。这种测试方法称为BDD(行为驱动开发)。这是最新的测试方法。它强调“行为”而不是“测试”。如果您有兴趣,可以阅读这篇文章。
如果你习惯了传统的TDD测试,你可以这样编写测试代码:
|
TEST_CASE( "向量可以调整大小和调整大小", "[向量]" ) {
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
SECTION( "调整大小以改变大小和容量" ) {
v.调整大小(10);
要求( v.size()==10 );
要求( v.capacity()=10 );
}
SECTION( "调整较小的大小会更改大小,但不会更改容量" ) {
v.调整大小(0);
要求( v.size()==0 );
要求( v.capacity()=5 );
}
SECTION( "保留更大的容量会改变容量,但不会改变大小" ) {
v. 保留(10);
要求( v.size()==5 );
要求( v.capacity()=10 );
}
SECTION( "保留较小的值不会改变大小或容量" ) {
v.reserve(0);
要求( v.size()==5 );
要求( v.capacity()=5 );
}
}
|
其实这两种方法是等价的。 SCENARIO只是TEST_CASE的别名,GIVEN、WHEN和THEN最终映射到SECTION。区别仅在于测试方式。您可以根据自己的喜好使用自己喜欢的方法。
SECTION 的执行顺序
上面的代码非常清晰易懂,但是有一点需要注意,那就是SECTION的执行方法。上一节的代码中,TEST_CASE中有4个SECTION,它们并不是纯粹的顺序执行关系。第一个SECTION执行完成后,会重新开始执行,并跳过已经执行过的SECTION。也就是说,上面代码的执行路径大致是这样的(去掉SECTION宏后):
|
//第1 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.调整大小(10);
要求( v.size()==10 );
要求( v.capacity()=10 );
//第2 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.调整大小(0);
要求( v.size()==0 );
要求( v.capacity()=5 );
//第3 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v. 保留(10);
要求( v.size()==5 );
要求( v.capacity()=10 );
//第4 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.reserve(0);
要求( v.size()==5 );
要求( v.capacity()=5 );
|
测试代码的执行入口
在C++中,任何需要执行的代码都必须经过main函数,测试代码也不例外。 Catch不需要我们自己编写main函数来调用这些测试代码。它提供了默认的main函数入口。您只需在(并且仅在)一个文件中添加以下配置宏:
|
#定义CATCH_CONFIG_MAIN
include "catch.hpp"
|
最佳实践
命令行参数
最佳做法是使用单独的文件来放置这两行代码,并将测试代码写入其他文件中。
原因是Catch是一个单一头文件库,这意味着它的内容最终会出现在所有包含这个头文件的编译单元中。如果我们把测试代码和上面两行代码放在一起,每次编译测试代码的时候都需要编译Catch内核,这样会导致编译速度非常非常慢。如果将两者分开的话,Catch的内核只需要在一个文件中编译一次(因为Catch内部已经做了判断,如果内核已经编译过了,就不需要再编译一次,即使你使用#在多个文件中包含“catch”.hpp”)。该文件的编译速度比较慢,但该文件不会改变,所以整个开发周期只需要编译一次,并且不断更新测试编译速度代码会快得多。
TAG
Catch提供的主函数实现的另一个强大功能是丰富的命令行参数。你可以选择执行部分TEST_CASE,也可以选择不执行部分TEST_CASE。您可以使用它来调整输出。 xml文件中,还可以用它从文件中读取需要测试的测试用例。这些命令的具体使用请参考Catch官方文档的命令行部分。
提供自己的 main 函数入口
需要注意的是,这些强大的命令行大多数都是基于TAG 的,它是TEST_CASE 定义中的第二个参数。
|
TEST_CASE( "向量可以调整大小和调整大小", "[向量]" )
|
在上面的定义中,“[vector]”是一个TAG,您可以提供多个TAG:
|
TEST_CASE( "D", "[小部件][小工具]" ) { /* . */}
|
此时,您可以在命令行中根据TAG选择是否执行TEST_CASE。例如:
|
./catch "[vector]" //只执行那些标记为向量的测试用例
|
另外,还可以使用一些特殊字符,如[.]来表示隐藏。 [.integration]表示默认隐藏,但可以在命令行中使用[.integration] TAG执行。其他一些特殊字符请参考官方文档的测试用例和章节部分。
|
./catch //默认不执行集成
./catch "[.integration]" //使用TAG进行积分
|
其他内容
如果不喜欢上面的处理方式,想自己提供main函数,可以使用CATCH_CONFIG_RUNNER。具体细节请查看官方文档中的Supplying main() myself部分。
如何使用它
其实Catch本身比较简单,不需要太多其他的学习。它的大部分用法都非常直观。看完它的官方教程,基本上就可以入门了,然后有时间慢慢看。阅读其官方文档集
提供额外配置
语法非常简单明了,用它编写的测试代码就像自然语言一样易于理解。
SECTION 的执行顺序
Catch 是一个单一头文件库,您只需#include“catch.hpp”即可。然后你可以像这样编写测试代码:
|
SCENARIO( "矢量可以调整大小和调整大小", "[矢量]" ) {
GIVEN( "带有一些项目的向量" ) {
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
WHEN( "尺寸增加" ) {
v.调整大小(10);
THEN( "大小和容量变化" ) {
要求( v.size()==10 );
要求( v.capacity()=10 );
}
}
WHEN( "尺寸减小" ) {
v.调整大小(0);
THEN( "大小改变但容量不变" ) {
要求( v.size()==0 );
要求( v.capacity()=5 );
}
}
WHEN( "保留更多容量" ) {
v. 保留(10);
THEN( "容量改变但大小不变" ) {
要求( v.size()==5 );
要求( v.capacity()=10 );
}
}
WHEN( "保留较少容量" ) {
v.reserve(0);
THEN( "大小和容量都没有改变" ) {
要求( v.size()==5 );
要求( v.capacity()=5 );
}
}
}
}
|
这是几乎可读的代码,不需要解释。这种测试方法称为BDD(行为驱动开发)。这是最新的测试方法。它强调“行为”而不是“测试”。如果您有兴趣,可以阅读这篇文章。
如果你习惯了传统的TDD测试,你可以这样编写测试代码:
|
TEST_CASE( "向量可以调整大小和调整大小", "[向量]" ) {
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
SECTION( "调整大小以改变大小和容量" ) {
v.调整大小(10);
要求( v.size()==10 );
要求( v.capacity()=10 );
}
SECTION( "调整较小的大小会更改大小,但不会更改容量" ) {
v.调整大小(0);
要求( v.size()==0 );
要求( v.capacity()=5 );
}
SECTION( "保留更大的容量会改变容量,但不会改变大小" ) {
v. 保留(10);
要求( v.size()==5 );
要求( v.capacity()=10 );
}
SECTION( "保留较小的值不会改变大小或容量" ) {
v.reserve(0);
要求( v.size()==5 );
要求( v.capacity()=5 );
}
}
|
其实这两种方法是等价的。 SCENARIO只是TEST_CASE的别名,GIVEN、WHEN和THEN最终映射到SECTION。区别仅在于测试方式。您可以根据自己的喜好使用自己喜欢的方法。
测试代码的执行入口
上面的代码非常清晰易懂,但是有一点需要注意,那就是SECTION的执行方法。上一节的代码中,TEST_CASE中有4个SECTION,它们并不是纯粹的顺序执行关系。第一个SECTION执行完成后,会重新开始执行,并跳过已经执行过的SECTION。也就是说,上面代码的执行路径大致是这样的(去掉SECTION宏后):
|
//第1 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.调整大小(10);
要求( v.size()==10 );
要求( v.capacity()=10 );
//第2 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.调整大小(0);
要求( v.size()==0 );
要求( v.capacity()=5 );
//第3 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v. 保留(10);
要求( v.size()==5 );
要求( v.capacity()=10 );
//第4 节
std:向量v(5);
要求( v.size()==5 );
要求( v.capacity()=5 );
v.reserve(0);
要求( v.size()==5 );
要求( v.capacity()=5 );
|
include "catch.hpp"
在C++中,任何需要执行的代码都必须经过main函数,测试代码也不例外。 Catch不需要我们自己编写main函数来调用这些测试代码。它提供了默认的main函数入口。您只需在(并且仅在)一个文件中添加以下配置宏:
|
#定义CATCH_CONFIG_MAIN
最佳实践
|
命令行参数
TAG
最佳做法是使用单独的文件来放置这两行代码,并将测试代码写入其他文件中。
原因是Catch是一个单一头文件库,这意味着它的内容最终会出现在所有包含这个头文件的编译单元中。如果我们把测试代码和上面两行代码放在一起,每次编译测试代码的时候都需要编译Catch内核,这样会导致编译速度非常非常慢。如果将两者分开的话,Catch的内核只需要在一个文件中编译一次(因为Catch内部已经做了判断,如果内核已经编译过了,就不需要再编译一次,即使你使用#在多个文件中包含“catch”.hpp”)。该文件的编译速度比较慢,但该文件不会改变,所以整个开发周期只需要编译一次,并且不断更新测试编译速度代码会快得多。
010-1010 Catch提供的主函数实现的另一个强大功能是丰富的命令行参数。你可以选择执行部分TEST_CASE,也可以选择不执行部分TEST_CASE。您可以使用它来调整输出。 xml文件中,还可以用它从文件中读取需要测试的测试用例。这些命令的具体使用请参考Catch官方文档的命令行部分。
010-1010 需要注意的是,这些强大的命令行大多数都是基于TAG 的,它是TEST_CASE 定义中的第二个参数。
|
TEST_CASE( "向量可以调整大小和调整大小", "[向量]" )
|
在上面的定义中,“[vector]”是一个TAG,您可以提供多个TAG:
|
TEST_CASE( "D", "[小部件][小工具]" ) { /* . */}
|
此时,您可以在命令行中根据TAG选择是否执行TEST_CASE。例如:
|
./catch "[vector]" //只执行那些标记为向量的测试用例
|
另外,还可以使用一些特殊字符,如[.]来表示隐藏。 [.integration]表示默认隐藏,但可以在命令行中使用[.integration] TAG执行。其他一些特殊字符请参考官方文档的测试用例和章节部分。
|
./catch //默认不执行集成
./catch "[.integration]" //使用TAG进行积分
|
【C++ 单元测试工具:探索 Catch 的强大功能】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
我一直在寻找一个功能强大、易于使用的 C++ 单元测试工具,看来 Catch 挺适合我的。
有8位网友表示赞同!
这个 Catch 工具听起来不错,想了解一下它的使用场景和学习难度如何?
有13位网友表示赞同!
对C++开发来说,单元测试真的很有必要,Catch 能帮助我们进行代码验证吗?
有19位网友表示赞同!
之前用过一些别的 C++ 单元测试框架,希望 Catch 能更方便简洁。
有20位网友表示赞同!
听说 Catch 支持断言非常完善,让我们更容易定位问题!
有8位网友表示赞同!
学习一个新的工具总需要时间,不过如果像 Catch 这样好用,值得花一点功夫去尝试。
有7位网友表示赞同!
对于 C++ 项目的开发,单元测试确实能提高代码质量,Catch 能提供很好的支持吗?
有19位网友表示赞同!
在网上看到过一些 Catch 的代码示例,感觉语法挺整洁的。
有20位网友表示赞同!
作为一个资深的 C++ 开发者,我一直在关注新的测试工具,这个 Catch 看起来很有潜力!
有8位网友表示赞同!
我想知道 Catch 支持哪些主流的编译器和 IDE 工具?
有8位网友表示赞同!
我很想尝试一把 Catch 使用感受,希望能够找到一些详细的教程和文档。
有17位网友表示赞同!
对新手来说,Catch 的学习成本有多高?是否有简便易懂的入门指南?
有16位网友表示赞同!
Catch 是否支持断点调试,这样能让我们更方便地分析测试结果吗?
有15位网友表示赞同!
这个 Catch 工具能够帮助我们快速生成测试用例吗?
有5位网友表示赞同!
我想知道 Catch 的社区是否活跃,有没有很多开发人员在使用和讨论它?
有7位网友表示赞同!
Catch 支持跨平台的使用吗?这对于我来说非常重要。
有16位网友表示赞同!
在实际项目中使用 Catch 的经验分享可以让我更好地了解它的功能和局限性吧?
有5位网友表示赞同!
如果我是 C++ 生涯初学者,推荐学习 Catch 作为第一套单元测试工具吗?
有6位网友表示赞同!