【解锁】Catch2——C++测试框架(Quick Start)

【解锁】Catch2——C++测试框架(Quick Start)

获取

有两种方法获取Catch2: 一种是直接下载头文件catch.hpp——推荐使用这种方式,可以简单的融入你的项目。 另一种是,获取catch2源码,https://github.com/catchorg/Catch2.git 适合二次开发或者学习里面的demo。

编译

因为我们今天要通过分析几个Catch2的examples来解锁Catch2的用法,所以用源码进行编译。

1.获取源码

git clone https://github.com/catchorg/Catch2.git

2.开启examples编译 cmake -DCATCH_BUILD_EXAMPLES=ON ../

(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 ,这个宏定义了catch2的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()。往往很多项目需要自己写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 );
            }
        }
    }
}