最近发现一个简单易用的C++开源测试开源库:Catch2(C++ Automated Test Cases in Headers),它的开源许可证是Boost license,当前版本是基于C++11开发的,最初版本Catch1.x是基于C++03/98的。
这里主要介绍Catch2, 以下均简称为catch。
在catch的文档指出,对于C++单元测试框架,目前已经有 Google Test, Boost.Test, CppUnit, Cute, 以及其它的一些,那么catch有什么优势呢,文档主要列举了以下这些优势:
其它一些关键特性有:
目前,有这些开源库均使用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)
一般的测试框架都采用基于类的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。
将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宏,像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。
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 );
REQUIRE_NOTHROW( expression )
CHECK_NOTHROW( expression )
REQUIRE_THROWS( expression )
CHECK_THROWS( expression )
REQUIRE_THROWS_AS( expression, exception type )
CHECK_THROWS_AS( expression, exception type )
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" );
REQUIRE_THROWS_MATCHES( expression, exception type, matcher for given exception type )
CHECK_THROWS_MATCHES( expression, exception type, matcher for given exception type )
Matchers顾名思义就是某个string或int或其它类型是否和Matcher所定义的条件match。
内置的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类型的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 }
浮点数类型的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.
通过继承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还支持其它一些高级功能,可以在用到的时候通过例子学习。