Catch2
Catch2是及其简单的C++测试框架,与gtest,boost.test和CppUnit相比Catch2非常小,甚至你只需要一个头文件就可以轻松的使用了。在小型项目里面可以很方便的用它搭建测试框架,同时配合一个更为简单的打桩框架stub,分分钟让你的测试用例跑起来。
今天,我们就来【解锁】Catch2。
获取
有两种方法获取Catch2:
一种是直接下载头文件catch.hpp——推荐使用这种方式,可以简单的融入你的项目。
另一种是,获取catch2源码,https://github.com/catchorg/Catch2.git 适合二次开发或者学习里面的demo。
编译
因为我们今天要通过分析几个Catch2的examples来解锁Catch2的用法,所以用源码进行编译。
- 获取源码
git clone https://github.com/catchorg/Catch2.git
- 开启examples编译
(base) frank@deepin:~/git/Catch2$ mkdir build
(base) frank@deepin:~/git/Catch2$ cd build/
(base) frank@deepin:~/git/Catch2/build$ cmake -DCATCH_BUILD_EXAMPLES=ON ../
-- The CXX compiler identification is GNU 9.2.0
-- Check for working CXX compiler: /usr/local/bin/c++
-- Check for working CXX compiler: /usr/local/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Found PythonInterp: /home/frank/miniconda3/bin/python (found version "3.7.4")
-- Examples included
-- Configuring done
-- Generating done
-- Build files have been written to: /home/frank/git/Catch2/build
(base) frank@deepin:~/git/Catch2/build$ make -j4
测试
先从一个最简单的例子开始。
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
int Factorial( int number ) {
return number <= 1 ? number : Factorial( number - 1 ) * number; // fail
// return number <= 1 ? 1 : Factorial( number - 1 ) * number; // pass
}
TEST_CASE( "Factorial of 0 is 1 (fail)", "[single-file]" ) {
REQUIRE( Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[single-file]" ) {
REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
REQUIRE( Factorial(10) == 3628800 );
}
第1行:#define CATCH_CONFIG_MAIN ,这个宏定义了catch的main函数。
// Standard C/C++ main entry point
int main (int argc, char * argv[]) {
return Catch::Session().run( argc, argv );
}
第3行:引入catch2,头文件,这里用的是"<...>",也就是使用编译安装的catch2,make后执行make install.咋装在系统目录。如果用单个头文件则应该使用"catch2.hpp",确认catch2.hpp
在你的攻宠目录活引入了其所在的头文件目录。
第5行:int Factorial( int number ) 是被测函数。
第10、14行:分别是两个测试用例
REQUIRE:是断言。
运行结果如下:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
010-TestCase is a Catch v2.11.1 host application.
Run with -? for options
-------------------------------------------------------------------------------
Factorial of 0 is 1 (fail)
-------------------------------------------------------------------------------
/home/frank/git/Catch2/examples/010-TestCase.cpp:13
...............................................................................
/home/frank/git/Catch2/examples/010-TestCase.cpp:14: FAILED:
REQUIRE( Factorial(0) == 1 )
with expansion:
0 == 1
===============================================================================
test cases: 2 | 1 passed | 1 failed
assertions: 5 | 4 passed | 1 failed
自己写main()
如果不想使用Catch2提供的main()函数,往往很多项目需要自己写main()函数,那么可以使用下面的方法。
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
int main( int argc, char* argv[] ) {
// global setup...
int result = Catch::Session().run( argc, argv );
// global clean-up...
return result;
}
如果想用自己的命令行参数,可以这样实现:
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
int main( int argc, char* argv[] )
{
Catch::Session session; // There must be exactly one instance
int height = 0; // Some user variable you want to be able to set
// Build a new parser on top of Catch's
using namespace Catch::clara;
auto cli
= session.cli() // Get Catch's composite command line parser
| Opt( height, "height" ) // bind variable to a new option, with a hint string
["-g"]["--height"] // the option names it will respond to
("how high?"); // description string for the help output
// Now pass the new composite back to Catch so it uses that
session.cli( cli );
// Let Catch (using Clara) parse the command line
int returnCode = session.applyCommandLine( argc, argv );
if( returnCode != 0 ) // Indicates a command line error
return returnCode;
// if set on the command line then 'height' is now set at this point
if( height > 0 )
std::cout << "height: " << height << std::endl;
return session.run();
}
SECTION
你在测试某个类的时候需要对这个类整体属性进行设置,可以采用setup()活teardow()的方式。每个方法都基于这些特定的属性,但往往每个成员方法有需要有不同的测试场景,这时SECTION就可以派上用场。
也就是说,这个类某一组属性的用例可以是TEST_CASE的范畴,在这个TEST_CASE范畴内成员方法的不同场景可以使SECTION范畴。
用下面的例子来说明:
#include <catch2/catch.hpp>
TEST_CASE( "vectors can be sized and resized", "[vector]" ) {
// For each section, vector v is anew:
std::vector<int> v( 5 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
SECTION( "resizing bigger changes size and capacity" ) {
v.resize( 10 );
REQUIRE( v.size() == 10 );
REQUIRE( v.capacity() >= 10 );
}
SECTION( "resizing smaller changes size but not capacity" ) {
v.resize( 0 );
REQUIRE( v.size() == 0 );
REQUIRE( v.capacity() >= 5 );
}
SECTION( "reserving bigger changes capacity but not size" ) {
v.reserve( 10 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 10 );
}
SECTION( "reserving smaller does not change size or capacity" ) {
v.reserve( 0 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
}
}
第7行:std::vector<int> v( 5 );
是类(TEST_CASE)的范畴。
第13,19,25,31行:是resize()
方法不同场景的测试,是方法(SECTION)范畴。
这时你可以用命令行参数来指定某一个SECTION执行。
(base) frank@deepin:~/git/Catch2/build/examples$ ./100-Fix-Section -c "resizing bigger changes size and capacity"
===============================================================================
All tests passed (4 assertions in 1 test case)
BDD风格
总体上和SECTION类似,只不过更接近自然语言或行为,BDD(行为驱动开发)——你可以把测试用例当做你的需求文档。
例子:
#include <catch2/catch.hpp>
SCENARIO( "vectors can be sized and resized", "[vector]" ) {
GIVEN( "A vector with some items" ) {
std::vector<int> v( 5 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
WHEN( "the size is increased" ) {
v.resize( 10 );
THEN( "the size and capacity change" ) {
REQUIRE( v.size() == 10 );
REQUIRE( v.capacity() >= 10 );
}
}
WHEN( "the size is reduced" ) {
v.resize( 0 );
THEN( "the size changes but not capacity" ) {
REQUIRE( v.size() == 0 );
REQUIRE( v.capacity() >= 5 );
}
}
WHEN( "more capacity is reserved" ) {
v.reserve( 10 );
THEN( "the capacity changes but not the size" ) {
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 10 );
}
}
WHEN( "less capacity is reserved" ) {
v.reserve( 0 );
THEN( "neither size nor capacity are changed" ) {
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
}
}
}
}