当前位置: 首页 > 工具软件 > Catch2 > 使用案例 >

catch2:一个好用的C++单元测试框架

敖永丰
2023-12-01

最近发现一个简单易用的C++开源测试开源库:Catch2(C++ Automated Test Cases in Headers),它的开源许可证是Boost license,当前版本是基于C++11开发的,最初版本Catch1.x是基于C++03/98的。
这里主要介绍Catch2, 以下均简称为catch。

为什么需要catch

在catch的文档指出,对于C++单元测试框架,目前已经有 Google Test, Boost.Test, CppUnit, Cute, 以及其它的一些,那么catch有什么优势呢,文档主要列举了以下这些优势:

  • 简单易用:只需要下载catch.hpp,包含到你的工程就可以了
  • 不依赖外部库:只要你可以编译C++11,有C++的标准库就可以了
  • 测试case可以分割为sections: 每个setcion都是独立的运行单元
  • 提供了BDD式的测试模式:可以使用Given-When-Then section来做BDD测试
  • 只用一个核心的assertion宏来做比较。用标准的C++运算符来做比较,但是可以分解表达式,记录表达式等号左侧和右侧的值
  • 可以用任何形式的字符串给测试命名,不用担心名字是否合法

其它一些关键特性有:

  • 可以给test case打tag, 因而可以很容易的只跑某个tag组的test cases
  • 输出通过reporter对象完成,支持基本的文本和XML格式输出测试结果,也支持自定义reporter
  • 支持JUnit xml输出,这样可以和第三方工具整合,如CI服务器等
  • 提供默认的main()函数,用户也可以使用自己的main()函数
  • 提供一个命令行解析工具,用户在使用自己的main()函数的时候可以添加命令行参数
  • catch软件可以做自测试
  • 提供可选的assertion宏,可以报告错误但不终止test case
  • 通过内置的Approx()语法支持可控制精度的浮点数比较
  • 通过Matchers支持各种形式的比较,也支持自定义的比较方法

目前,有这些开源库均使用catch作为测试框架,也有包含NASA在内的社会组织使用了Catch2。下面分别介绍catch的一些关键特性:

简单易用

Catch是一个header-only的开源库,这意味着你只需要把一个头文件放到系统或者你的工程的某个目录,编译的时候指向它就可以了。

下面用一个Catch文档中的小例子说明如何使用Catch,假设你写了一个求阶乘的函数:

int Factorial( int number ) {
   return number <= 1 ? number : Factorial( number - 1 ) * number; 
}

为了简单起见,将被测函数和要测试代码放在一个文件中,你只需要在这个函数之前加入两行:

#define CATCH_CONFIG_MAIN

#include <catch2/catch.hpp>

第一行的作用是由catch提供一个main函数,第二行的作用是包含测试所需要的头文件,假设最后的文件为catchTest.cpp,假设相关的文件安装到了/usr/local/include下,下面这样编译就可以了:

g++ -std=c++11 -o catchTest catchTest.cpp -I/usr/local/include/

运行一下,结果为:

[qsun @T440p examples]$./catchTest 
===============================================================================
No tests ran

那么如何加入一个test case呢,很简单:

TEST_CASE() {
    REQUIRE(Factorial(2) == 2);
}

当然你也可以为你的TEST_CASE起名字,或者加标签:

TEST_CASE(”Test with number big than 0", "[tag1]") {
    REQUIRE(Factorial(2) == 2);
}

”Test with number big than 0"是test case的名字,全局必须唯一, "tag1"是标签名,需要放在[]内部,一个test case可以有多个标签,多个test case可以使用相同的标签。REQUIRE是一个assert宏,用来判断是否相等。

命令行选项

以上面的这个简单程序为例,可以通过下面-?来查询命令行选项参数:

> [qsun @T440p examples]$./catchTest -?

Catch v2.6.1
usage:
  catchTest [<test name|pattern|tags> ... ] options

where options are:
  -?, -h, --help                            display usage information
  -l, --list-tests                          list all/matching test cases
  -t, --list-tags                           list all/matching tags
  -s, --success                             include successful tests in
                                            output
  -b, --break                               break into debugger on failure
  -e, --nothrow                             skip exception tests
  -i, --invisibles                          show invisibles (tabs, newlines)
  -o, --out <filename>                      output filename
  -r, --reporter <name>                     reporter to use (defaults to
                                            console)
  -n, --name <name>                         suite name
  -a, --abort                               abort at first failure
  -x, --abortx <no. failures>               abort after x failures
  -w, --warn <warning name>                 enable warnings
  -d, --durations <yes|no>                  show test durations
  -f, --input-file <filename>               load test names to run from a
                                            file
  -#, --filenames-as-tags                   adds a tag for the filename
  -c, --section <section name>              specify section to run
  -v, --verbosity <quiet|normal|high>       set output verbosity
  --list-test-names-only                    list all/matching test cases
                                            names only
  --list-reporters                          list all reporters
  --order <decl|lex|rand>                   test case order (defaults to
                                            decl)
  --rng-seed <'time'|number>                set a specific seed for random
                                            numbers
  --use-colour <yes|no>                     should output be colourised
  --libidentify                             report name and version according
                                            to libidentify standard
  --wait-for-keypress <start|exit|both>     waits for a keypress before
                                            exiting
  --benchmark-resolution-multiple           multiple of clock resolution to
  <multiplier>                              run benchmarks

For more detailed usage please see the project docs

一些比较常见的命令行选项的使用如下:

  • 显示test case总体情况:

    [qsun @T440p examples]$./catchTest -l
    All available test cases:
      Test with number big than 0
          [tag1]
    1 test case
    
    
  • 显示所有的标签(tags):

    [qsun @T440p examples]$./catchTest -t
    All available tags:
       1  [tag1]
    1 tag
    
  • 运行某个tag下的所有test cases:

    [qsun @T440p examples]$./catchTest [tag1]
    ===============================================================================
    All tests passed (1 assertion in 1 test case)
    
  • 运行某个名字的test case:

    [qsun @T440p examples]$./catchTest "Test with number big than 0"
    ===============================================================================
    All tests passed (1 assertion in 1 test case)
    

Sections

一般的测试框架都采用基于类的test fixture, 通常需要定义setup()和teardown()函数(或者在构造/析构函数中做类似的事情)。catch不仅全面支持test fixture模式,还提供了一种section机制:每个section都是独立运行单元,比如下面:

TEST_CASE( "vectors can be sized and resized", "[vector]" ) {

    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 );
    }
}

对于每个section来说,它都从test case的最开始运行,也就是说它们共享前半部分的代码,这样,在进入section之前,我们知道v的size为5,capacity大于5。根据section的实现,这些section不是并发执行的,而是每次执行一个section。

BDD-style

将test case和section重命名就可以得到一种BDD模式的测试,TDD(测试驱动开发)和BDD(行为驱动开发)是两种比较流行的开发模式。下面是一个例子

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 );
            }
        }
    }
}

下面是编译和输出结果:

[qsun @T440p examples]$g++ -o 120-Bdd-ScenarioGivenWhenThen 120-Bdd-ScenarioGivenWhenThen.cpp 000-CatchMain.o
[qsun @T440p examples]$./120-Bdd-ScenarioGivenWhenThen --reporter compact --success
120-Bdd-ScenarioGivenWhenThen.cpp:12: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:13: passed: v.capacity() >= 5 for: 5 >= 5
120-Bdd-ScenarioGivenWhenThen.cpp:19: passed: v.size() == 10 for: 10 == 10
120-Bdd-ScenarioGivenWhenThen.cpp:20: passed: v.capacity() >= 10 for: 10 >= 10
120-Bdd-ScenarioGivenWhenThen.cpp:12: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:13: passed: v.capacity() >= 5 for: 5 >= 5
120-Bdd-ScenarioGivenWhenThen.cpp:27: passed: v.size() == 0 for: 0 == 0
120-Bdd-ScenarioGivenWhenThen.cpp:28: passed: v.capacity() >= 5 for: 5 >= 5
120-Bdd-ScenarioGivenWhenThen.cpp:12: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:13: passed: v.capacity() >= 5 for: 5 >= 5
120-Bdd-ScenarioGivenWhenThen.cpp:35: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:36: passed: v.capacity() >= 10 for: 10 >= 10
120-Bdd-ScenarioGivenWhenThen.cpp:12: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:13: passed: v.capacity() >= 5 for: 5 >= 5
120-Bdd-ScenarioGivenWhenThen.cpp:43: passed: v.size() == 5 for: 5 == 5
120-Bdd-ScenarioGivenWhenThen.cpp:44: passed: v.capacity() >= 5 for: 5 >= 5
Passed 1 test case with 16 assertions.

也可以使用下面的两个宏来使用链式的WHEN和THEN

AND_WHEN( something )
AND_THEN( something )

作者的selftest中有相关的代码,如下:

/*
 *  Created by Phil on 29/11/2010.
 *  Copyright 2010 Two Blue Cubes Ltd. All rights reserved.
 *
 *  Distributed under the Boost Software License, Version 1.0. (See accompanying
 *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
 */

#include "catch2/catch.hpp"

namespace { namespace BDDTests {

#ifndef BDD_TEST_HELPERS_INCLUDED // Don't compile this more than once per TU
#define BDD_TEST_HELPERS_INCLUDED

    inline bool itDoesThis() { return true; }

    inline bool itDoesThat() { return true; }

    namespace {

// a trivial fixture example to support SCENARIO_METHOD tests
        struct Fixture {
            Fixture()
                    : d_counter(0) {
            }

            int counter() {
                return d_counter++;
            }

            int d_counter;
        };

    }
#endif

    SCENARIO("Do that thing with the thing", "[Tags]") {
        GIVEN("This stuff exists") {
            // make stuff exist
            AND_GIVEN("And some assumption") {
                // Validate assumption
                WHEN("I do this") {
                    // do this
                    THEN("it should do this") {
                        REQUIRE(itDoesThis());
                        AND_THEN("do that")REQUIRE(itDoesThat());
                    }
                }
            }
        }
    }

    SCENARIO("Vector resizing affects size and capacity", "[vector][bdd][size][capacity]") {
        GIVEN("an empty vector") {
            std::vector<int> v;
            REQUIRE(v.size() == 0);

            WHEN("it is made larger") {
                v.resize(10);
                THEN("the size and capacity go up") {
                    REQUIRE(v.size() == 10);
                    REQUIRE(v.capacity() >= 10);

                    AND_WHEN("it is made smaller again") {
                        v.resize(5);
                        THEN("the size goes down but the capacity stays the same") {
                            REQUIRE(v.size() == 5);
                            REQUIRE(v.capacity() >= 10);
                        }
                    }
                }
            }

            WHEN("we reserve more space") {
                v.reserve(10);
                THEN("The capacity is increased but the size remains the same") {
                    REQUIRE(v.capacity() >= 10);
                    REQUIRE(v.size() == 0);
                }
            }
        }
    }

    SCENARIO("This is a really long scenario name to see how the list command deals with wrapping",
             "[very long tags][lots][long][tags][verbose]"
                     "[one very long tag name that should cause line wrapping writing out using the list command]"
                     "[anotherReallyLongTagNameButThisOneHasNoObviousWrapPointsSoShouldSplitWithinAWordUsingADashCharacter]") {
        GIVEN("A section name that is so long that it cannot fit in a single console width")WHEN(
                    "The test headers are printed as part of the normal running of the scenario")THEN(
                        "The, deliberately very long and overly verbose (you see what I did there?) section names must wrap, along with an indent")SUCCEED(
                            "boo!");
    }

    SCENARIO_METHOD(Fixture,
                    "BDD tests requiring Fixtures to provide commonly-accessed data or methods",
                    "[bdd][fixtures]") {
        const int before(counter());
        GIVEN("No operations precede me") {
            REQUIRE(before == 0);
            WHEN("We get the count") {
                const int after(counter());
                THEN("Subsequently values are higher") {
                    REQUIRE(after > before);
                }
            }
        }
    }

}} // namespace BDDtests

从源码可以知道,上面的这些GIVEN, WHEN, THEN等最后其实都展开为SECTION宏:

// "BDD-style" convenience wrappers
#define SCENARIO( ... ) TEST_CASE( "Scenario: " __VA_ARGS__ )
#define SCENARIO_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " __VA_ARGS__ )
 
#define GIVEN( desc )    SECTION( std::string("   Given: ") + desc )
#define WHEN( desc )     SECTION( std::string("    When: ") + desc )
#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc )
#define THEN( desc )     SECTION( std::string("    Then: ") + desc )
#define AND_THEN( desc ) SECTION( std::string("     And: ") + desc )

Assertion Macros

上面其实已经提到了一些assertion宏,像REQUIRE等,这里全面的介绍下。
常用的有REQUIRE系列和CHECK系列:

REQUIRE( expression )
CHECK( expression )

REQUIRE宏在expression为false时将会终止当前test case, CHECK在expression为false时会给出警告信息但当前test case继续往下执行。
对应的还有:

REQUIRE_FALSE( expression )
CHECK_FALSE( expression )

REQUIRE_FALSE宏在expression为true时将会终止当前test case, CHECK_FALSE在expression为true时会给出警告信息但当前test case继续往下执行。

这里有一点要指出的是,对于:

CHECK(a == 1 && b == 2)
CHECK( a == 2 || b == 1 )

这样的语句,由于宏展开的原因,catch不能通过编译,需要在表达式的两端加上括号:

CHECK((a == 1 && b == 2))
CHECK((a == 2 || b == 1 ))

我们在最初求阶乘的例子上进行测试,代码如下:


TEST_CASE("Test with number big than 0", "[tag1]")
{
    REQUIRE(Factorial(2) == 2);
    // REQUIRE((Factorial(3) == 6) && (Factorial(4) == 24)); cannot compile
    CHECK(Factorial(0) == 1);
    REQUIRE((Factorial(3) == 6 && Factorial(4) == 24));
}

输出为:

[qsun @T440p examples]$./catchTest -r compact --success
catchTest.cpp:9: passed: Factorial(2) == 2 for: 2 == 2
catchTest.cpp:11: failed: Factorial(0) == 1 for: 0 == 1
catchTest.cpp:12: passed: (Factorial(3) == 6 && Factorial(4) == 24) for: true
Failed 1 test case, failed 1 assertion.

注意到,这里Factorial(0) == 1使用的是CHECK,由于0!= 1,故该测试失败,但因为我们用的是CHECK,故这里只是给出了警告信息,没有终止test case。

Floating point comparisons

catch2支持比较全面的浮点数比较,可能是作者在银行工作,这个测试框架也是针对作者写的银行业务的代码,这些代码对数值比较的要求较多。
具体的说catch2浮点数比较采用类Approx, Approx采用数值初始化,同时支持以下三种属性:

  • epsilon:相当于误差相对于原值的百分比值,比如epsilon=0.01,则意味着与其比较的值在该Approx值的%1的范围均视为相等。默认值为 std::numeric_limits::epsilon()*100

     Approx target = Approx(100).epsilon(0.01);
     100.0 == target; // Obviously true
     200.0 == target; // Obviously still false
     100.5 == target; // True, because we set target to allow up to 1% difference
    
  • margin: epsilon是一个相对的百分比值,margin是一个绝对值,其默认值为0。比如:

      Approx target = Approx(100).margin(5);
      100.0 == target; // Obviously true
      200.0 == target; // Obviously still false
      104.0 == target; // True, because we set target to allow absolute difference of at most 5
    
  • scale: 有时比较的两边采用不同的量级,采用scale后,Approx允许的误差为:(Approx::scale + Approx::value)* epsilon, 其默认值为0. 比如:

      Approx target = Approx(100).scale(100).epsilon(0.01);
      100.0 == target; //  true
      101.5 == target; //  true
      200.0 == target; // false
      100.5 == target; // True, because we set target to allow up to 1% difference, and final target value is 100	
      
      
      Approx target1 = Approx(100).scale(100).margin(5);
      100.0 == target1; // true
      200.0 == target1; // false
      104.0 == target1; // True, because we set target to allow absolute difference of at most 5	
    

查看Catch2的源码,可以找到Approx的实现如下:


bool Approx::equalityComparisonImpl(const double other) const {
    // First try with fixed margin, then compute margin based on epsilon, scale and Approx's value
    // Thanks to Richard Harris for his help refining the scaled margin value
    return marginComparison(m_value, other, m_margin)
            || marginComparison(m_value, other, m_epsilon * (m_scale + std::fabs(std::isinf(m_value)? 0 : m_value)));
}  
 
// Performs equivalent check of std::fabs(lhs - rhs) <= margin
// But without the subtraction to allow for INFINITY in comparison
bool marginComparison(double lhs, double rhs, double margin) {
    return (lhs + margin >= rhs) && (rhs + margin >= lhs);
}

因此我们不需要显式的设置epsilon, scale, 或者 margin,一般情况下使用它们的默认值就可以了:

REQUIRE( performComputation() == Approx( 2.1 ) );

类似C++11,Catch2也为用户定义了一个替代字符_a, 这样使用时不用每次都写Approx, 只需要在前部包含命名空间就可以了:

using namespace Catch::literals;
REQUIRE( performComputation() == 2.1_a );

Exceptions

  • 下面两个宏用来测试expression中没有异常抛出,满足条件则assertion为true

REQUIRE_NOTHROW( expression )
CHECK_NOTHROW( expression )

  • 下面两个宏用来测试expression中异常抛出,满足条件则assertion为true

REQUIRE_THROWS( expression )
CHECK_THROWS( expression )

  • 下面两个宏用来测试expression中有某种类型的异常抛出,满足条件则assertion为true

REQUIRE_THROWS_AS( expression, exception type )
CHECK_THROWS_AS( expression, exception type )

  • 下面两个宏用来测试expression中异常名包含某个string的异常或符合某种string matcher的异常抛出,满足条件则assertion为true

REQUIRE_THROWS_WITH( expression, string or string matcher )
CHECK_THROWS_WITH( expression, string or string matcher )

例如:

REQUIRE_THROWS_WITH( openThePodBayDoors(), Contains( "afraid" ) && Contains( "can't do that" ) );
REQUIRE_THROWS_WITH( dismantleHal(), "My mind is going" );
  • 下面两个宏用来测试expression中有某种类型且符合某种string matcher的异常抛出,满足条件则assertion为true

REQUIRE_THROWS_MATCHES( expression, exception type, matcher for given exception type )
CHECK_THROWS_MATCHES( expression, exception type, matcher for given exception type )

Matchers

Matchers顾名思义就是某个string或int或其它类型是否和Matcher所定义的条件match。

String matchers

内置的string matchers有StartsWith, EndsWith, Contains, Equals 和 Matches,前四个是常见的string或substring的比较:

例如,验证某个string是否以某个string结束:

using Catch::Matchers::EndsWith; // or Catch::EndsWith
std::string str = getStringFromSomewhere();
REQUIRE_THAT( str, EndsWith( "as a service" ) )

EndsWith也支持是否大小写采用大小写匹配:

REQUIRE_THAT( str, EndsWith( "as a service", Catch::CaseSensitive::No ) ); 

也支持多个Match串联:

REQUIRE_THAT( str, 
    EndsWith( "as a service" ) || 
    (StartsWith( "Big data" ) && !Contains( "web scale" ) ) );

最后一个Matches是matchECMAScript类型的正则表达式,例如:

using Catch::Matchers::Matches;
REQUIRE_THAT(std::string("this string contains 'abc' as a substring"),
                         Matches("this string CONTAINS 'abc' as a substring", Catch::CaseSensitive::No));

Vector matchers

vector类型的match有Contains, VectorContains 和Equals. VectorContains判断某个vector内部是否有某个元素,Contains判断某个vector是否包含另外一个vector。
下面是来自selftest的一个例子:

TEST_CASE("Vector matchers", "[matchers][vector]") {
    using Catch::Matchers::VectorContains;
    using Catch::Matchers::Contains;
    using Catch::Matchers::UnorderedEquals;
    using Catch::Matchers::Equals;
    std::vector<int> v;
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);

    std::vector<int> v2;
    v2.push_back(1);
    v2.push_back(2);

    std::vector<int> empty;

    SECTION("Contains (element)") {
        CHECK_THAT(v, VectorContains(1));
        CHECK_THAT(v, VectorContains(2));
    }
    SECTION("Contains (vector)") {
        CHECK_THAT(v, Contains(v2));
        v2.push_back(3); // now exactly matches
        CHECK_THAT(v, Contains(v2));

        CHECK_THAT(v, Contains(empty));
        CHECK_THAT(empty, Contains(empty));
    }
    SECTION("Contains (element), composed") {
        CHECK_THAT(v, VectorContains(1) && VectorContains(2));
    }

    SECTION("Equals") {

        // Same vector
        CHECK_THAT(v, Equals(v));

        CHECK_THAT(empty, Equals(empty));

        // Different vector with same elements
        v2.push_back(3);
        CHECK_THAT(v, Equals(v2));
    }
    SECTION("UnorderedEquals") {
        CHECK_THAT(v, UnorderedEquals(v));
        CHECK_THAT(empty, UnorderedEquals(empty));

        auto permuted = v;
        std::next_permutation(begin(permuted), end(permuted));
        REQUIRE_THAT(permuted, UnorderedEquals(v));

        std::reverse(begin(permuted), end(permuted));
        REQUIRE_THAT(permuted, UnorderedEquals(v));
    }
}

程序运行结果为:

[qsun @T440p examples]$./catchTest [vector] --success -r compact
catchTest.cpp:51: passed: v, VectorContains(1) for: { 1, 2, 3 } Contains: 1
catchTest.cpp:52: passed: v, VectorContains(2) for: { 1, 2, 3 } Contains: 2
catchTest.cpp:55: passed: v, Contains(v2) for: { 1, 2, 3 } Contains: { 1, 2 }
catchTest.cpp:57: passed: v, Contains(v2) for: { 1, 2, 3 } Contains: { 1, 2, 3 }
catchTest.cpp:59: passed: v, Contains(empty) for: { 1, 2, 3 } Contains: {  }
catchTest.cpp:60: passed: empty, Contains(empty) for: {  } Contains: {  }
catchTest.cpp:63: passed: v, VectorContains(1) && VectorContains(2) for: { 1, 2, 3 } ( Contains: 1 and Contains: 2 )
catchTest.cpp:69: passed: v, Equals(v) for: { 1, 2, 3 } Equals: { 1, 2, 3 }
catchTest.cpp:71: passed: empty, Equals(empty) for: {  } Equals: {  }
catchTest.cpp:75: passed: v, Equals(v2) for: { 1, 2, 3 } Equals: { 1, 2, 3 }
catchTest.cpp:78: passed: v, UnorderedEquals(v) for: { 1, 2, 3 } UnorderedEquals: { 1, 2, 3 }
catchTest.cpp:79: passed: empty, UnorderedEquals(empty) for: {  } UnorderedEquals: {  }
catchTest.cpp:83: passed: permuted, UnorderedEquals(v) for: { 1, 3, 2 } UnorderedEquals: { 1, 2, 3 }
catchTest.cpp:86: passed: permuted, UnorderedEquals(v) for: { 2, 3, 1 } UnorderedEquals: { 1, 2, 3 }

Floating point matchers

浮点数类型的matchers有两种: WithinULP 和 WithinAbs,WithinAbs比较两个浮点数是差的绝对值是否小于某个值;WithinULP做ULP类型的检查。
下面是一个例子:

TEST_CASE("Floating point matchers: float", "[matchers][ULP]") {
    using Catch::Matchers::WithinAbs;
    using Catch::Matchers::WithinULP;

    SECTION("Margin") {
        REQUIRE_THAT(1.f, WithinAbs(1.f, 0));
        REQUIRE_THAT(0.f, WithinAbs(1.f, 1));

        REQUIRE_THAT(0.f, !WithinAbs(1.f, 0.99f));
        REQUIRE_THAT(0.f, !WithinAbs(1.f, 0.99f));

        REQUIRE_THAT(0.f, WithinAbs(-0.f, 0));
        REQUIRE_THAT(NAN, !WithinAbs(NAN, 0));

        REQUIRE_THAT(11.f, !WithinAbs(10.f, 0.5f));
        REQUIRE_THAT(10.f, !WithinAbs(11.f, 0.5f));
        REQUIRE_THAT(-10.f, WithinAbs(-10.f, 0.5f));
        REQUIRE_THAT(-10.f, WithinAbs(-9.6f, 0.5f));
    }
    SECTION("ULPs") {
        REQUIRE_THAT(1.f, WithinULP(1.f, 0));
        REQUIRE_THAT(1.1f, !WithinULP(1.f, 0));


        REQUIRE_THAT(1.f, WithinULP(1.f, 0));
        REQUIRE_THAT(-0.f, WithinULP(0.f, 0));

        REQUIRE_THAT(NAN, !WithinULP(NAN, 123));
    }
    SECTION("Composed") {
        REQUIRE_THAT(1.f, WithinAbs(1.f, 0.5) || WithinULP(1.f, 1));
        REQUIRE_THAT(1.f, WithinAbs(2.f, 0.5) || WithinULP(1.f, 0));

        REQUIRE_THAT(NAN, !(WithinAbs(NAN, 100) || WithinULP(NAN, 123)));
    }


}

运行结果为:

[qsun @T440p examples]$./catchTest [ULP] --success -r compact
catchTest.cpp:95: passed: 1.f, WithinAbs(1.f, 0) for: 1.0f is within 0.0 of 1.0
catchTest.cpp:96: passed: 0.f, WithinAbs(1.f, 1) for: 0.0f is within 1.0 of 1.0
catchTest.cpp:98: passed: 0.f, !WithinAbs(1.f, 0.99f) for: 0.0f not is within 0.9900000095 of 1.0
catchTest.cpp:99: passed: 0.f, !WithinAbs(1.f, 0.99f) for: 0.0f not is within 0.9900000095 of 1.0
catchTest.cpp:101: passed: 0.f, WithinAbs(-0.f, 0) for: 0.0f is within 0.0 of -0.0
catchTest.cpp:102: passed: (__builtin_nanf ("")), !WithinAbs((__builtin_nanf ("")), 0) for: nanf not is within 0.0 of nan
catchTest.cpp:104: passed: 11.f, !WithinAbs(10.f, 0.5f) for: 11.0f not is within 0.5 of 10.0
catchTest.cpp:105: passed: 10.f, !WithinAbs(11.f, 0.5f) for: 10.0f not is within 0.5 of 11.0
catchTest.cpp:106: passed: -10.f, WithinAbs(-10.f, 0.5f) for: -10.0f is within 0.5 of -10.0
catchTest.cpp:107: passed: -10.f, WithinAbs(-9.6f, 0.5f) for: -10.0f is within 0.5 of -9.6000003815
catchTest.cpp:110: passed: 1.f, WithinULP(1.f, 0) for: 1.0f is within 0 ULPs of 1.0f
catchTest.cpp:111: passed: 1.1f, !WithinULP(1.f, 0) for: 1.1f not is within 0 ULPs of 1.0f
catchTest.cpp:114: passed: 1.f, WithinULP(1.f, 0) for: 1.0f is within 0 ULPs of 1.0f
catchTest.cpp:115: passed: -0.f, WithinULP(0.f, 0) for: -0.0f is within 0 ULPs of 0.0f
catchTest.cpp:117: passed: (__builtin_nanf ("")), !WithinULP((__builtin_nanf ("")), 123) for: nanf not is within 123 ULPs of nanf
catchTest.cpp:120: passed: 1.f, WithinAbs(1.f, 0.5) || WithinULP(1.f, 1) for: 1.0f ( is within 0.5 of 1.0 or is within 1 ULPs of 1.0f )
catchTest.cpp:121: passed: 1.f, WithinAbs(2.f, 0.5) || WithinULP(1.f, 0) for: 1.0f ( is within 0.5 of 2.0 or is within 0 ULPs of 1.0f )
catchTest.cpp:123: passed: (__builtin_nanf ("")), !(WithinAbs((__builtin_nanf ("")), 100) || WithinULP((__builtin_nanf ("")), 123)) for: nanf not ( is within 100.0 of nan or is within 123 ULPs of nanf )
Passed 1 test case with 18 assertions.

Custom matchers

通过继承MatchBase, 可以自定义用户的matcher, Catch::MatcherBase这里的T是用户想要做match的数据类型。同时还需要重写match和describe两个函数。

如下,是一个判断某个数是否在某个范围类的自定义matcher:

// The matcher class
class IntRange : public Catch::MatcherBase<int> {
    int m_begin, m_end;
public:
    IntRange( int begin, int end ) : m_begin( begin ), m_end( end ) {}

    // Performs the test for this matcher
    virtual bool match( int const& i ) const override {
        return i >= m_begin && i <= m_end;
    }

    // Produces a string describing what this matcher does. It should
    // include any provided data (the begin/ end in this case) and
    // be written as if it were stating a fact (in the output it will be
    // preceded by the value under test).
    virtual std::string describe() const {
        std::ostringstream ss;
        ss << "is between " << m_begin << " and " << m_end;
        return ss.str();
    }
};

// The builder function
inline IntRange IsBetween( int begin, int end ) {
    return IntRange( begin, end );
}

// ...

// Usage
TEST_CASE("Integers are within a range")
{
    CHECK_THAT( 3, IsBetween( 1, 10 ) );
    CHECK_THAT( 100, IsBetween( 1, 10 ) );
}

运行上面的例子,结果为:

/**/TestFile.cpp:123: FAILED:
  CHECK_THAT( 100, IsBetween( 1, 10 ) )
with expansion:
  100 is between 1 and 10

此外,catcher还支持其它一些高级功能,可以在用到的时候通过例子学习。

 类似资料: