Loading

Catch2 - 用于 test 的轻量级库

 

此部分的主要内容来自:https://blog.csdn.net/ithiker/article/details/87909651

 

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

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

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

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

#define CATCH_CONFIG_MAIN
#include <catch.hpp>

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

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

后面的头文件路径可以不要

运行一下,结果为:

===============================================================================
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 宏,用来判断是否相等。

 

命令行选项

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

./catchTest -?

你会得到选项参数

Catch v2.13.8
usage:
  test [<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
  -D, --min-duration <seconds>              show test durations for tests
                                            taking at least the given number
                                            of seconds
  -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 <never|start|exit     waits for a keypress before
  |both>                                    exiting
  --benchmark-samples <samples>             number of samples to collect
                                            (default: 100)
  --benchmark-resamples <resamples>         number of resamples for the
                                            bootstrap (default: 100000)
  --benchmark-confidence-interval           confidence interval for the
  <confidence interval>                     bootstrap (between 0 and 1,
                                            default: 0.95)
  --benchmark-no-analysis                   perform only measurements; do not
                                            perform any analysis
  --benchmark-warmup-time                   amount of time in milliseconds
  <benchmarkWarmupTime>                     spent on warming up each test
                                            (default: 100)

For more detailed usage please see the project docs

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

显示 test case 总体情况:

./catchTest -l

得到结果:

All available test cases:
  Anonymous test case 1
1 test case

 

显示所有的标签(tags):

./catchTest -t

得到结果:

All available tags:
0 tags

 

运行某个tag下的所有 test cases :

./catchTest [tag1]

 

运行某个名字的 test case :

./catchTest "Test with number big than 0"

 

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

 

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

其中的 GIVEN WHEN THEN 都是用于测试结果的解释。

 

添加参数: success 输出成功结果, reporter 指定输出格式为 compact 简洁的

./test --reporter compact --success

输出结果:

test.cpp:11: passed: v.size() == 5 for: 5 == 5
test.cpp:12: passed: v.capacity() >= 5 for: 5 >= 5
test.cpp:20: passed: v.size() == 10 for: 10 == 10
test.cpp:21: passed: v.capacity() >= 10 for: 10 >= 10
test.cpp:11: passed: v.size() == 5 for: 5 == 5
test.cpp:12: passed: v.capacity() >= 5 for: 5 >= 5
test.cpp:30: passed: v.size() == 0 for: 0 == 0
test.cpp:31: passed: v.capacity() >= 5 for: 5 >= 5
test.cpp:11: passed: v.size() == 5 for: 5 == 5
test.cpp:12: passed: v.capacity() >= 5 for: 5 >= 5
test.cpp:40: passed: v.size() == 5 for: 5 == 5
test.cpp:41: passed: v.capacity() >= 10 for: 10 >= 10
test.cpp:11: passed: v.size() == 5 for: 5 == 5
test.cpp:12: passed: v.capacity() >= 5 for: 5 >= 5
test.cpp:50: passed: v.size() == 5 for: 5 == 5
test.cpp:51: passed: v.capacity() >= 5 for: 5 >= 5
Passed 1 test case with 16 assertions.

 

如果直接输出成功结果

./test --success

输出结果:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test is a Catch v2.13.8 host application.
Run with -? for options

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
-------------------------------------------------------------------------------
test.cpp:7
...............................................................................

test.cpp:11: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:12: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
       When: the size is increased
       Then: the size and capacity change
-------------------------------------------------------------------------------
test.cpp:18
...............................................................................

test.cpp:20: PASSED:
  REQUIRE( v.size() == 10 )
with expansion:
  10 == 10

test.cpp:21: PASSED:
  REQUIRE( v.capacity() >= 10 )
with expansion:
  10 >= 10

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
-------------------------------------------------------------------------------
test.cpp:7
...............................................................................

test.cpp:11: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:12: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
       When: the size is reduced
       Then: the size changes but not capacity
-------------------------------------------------------------------------------
test.cpp:28
...............................................................................

test.cpp:30: PASSED:
  REQUIRE( v.size() == 0 )
with expansion:
  0 == 0

test.cpp:31: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
-------------------------------------------------------------------------------
test.cpp:7
...............................................................................

test.cpp:11: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:12: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
       When: more capacity is reserved
       Then: the capacity changes but not the size
-------------------------------------------------------------------------------
test.cpp:38
...............................................................................

test.cpp:40: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:41: PASSED:
  REQUIRE( v.capacity() >= 10 )
with expansion:
  10 >= 10

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
-------------------------------------------------------------------------------
test.cpp:7
...............................................................................

test.cpp:11: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:12: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

-------------------------------------------------------------------------------
Scenario: vectors can be sized and resized
      Given: A vector with some items
       When: less capacity is reserved
       Then: neither size nor capacity are changed
-------------------------------------------------------------------------------
test.cpp:48
...............................................................................

test.cpp:50: PASSED:
  REQUIRE( v.size() == 5 )
with expansion:
  5 == 5

test.cpp:51: PASSED:
  REQUIRE( v.capacity() >= 5 )
with expansion:
  5 >= 5

===============================================================================
All tests passed (16 assertions in 1 test case)

 

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

AND_WHEN( something )
AND_THEN( something )
AND_GIVEN( something )

其使用方式与上面相同

 

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

编译运行得到:

~/work/test/catch$ ./test -r compact -s
test.cpp:11: passed: Factorial(2) == 2 for: 2 == 2
test.cpp:13: failed: Factorial(0) == 1 for: 0 == 1
test.cpp:14: 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( 1 == Approx( 2.1 ) );

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

using namespace Catch::literals;
REQUIRE( 1 == 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));
    }
}

 

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

 

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 ) );
}
posted @ 2022-03-13 22:59  Bluemultipl  阅读(992)  评论(0编辑  收藏  举报