本篇基于 Catch2 版本:2.13.0 / 2020-7-19 DOC翻译整理
Catch 的断言宏主要分两种,REQUIRE 族当失败时停止测试与 CHECK 族失败后继续执行。
CHECK( str == "string value" );
CHECK( thisReturnsTrue() );
REQUIRE( i == 42 );
另外提供两个逻辑非的断言宏
REQUIRE_FALSE( thisReturnsFalse() );
注意 Catch 并不支持复杂的逻辑判断,如:
CHECK(a == 1 && b == 2);
比较浮点需要指定精度,Catch 提供了一种称为 Approx 的包装器类来执行浮点值比较的方法。同时提供了一个简写方式 _a
,注意它在 Catch::literals 命名空间下。
REQUIRE( performComputation() == Approx( 2.1 ) );
using namespace Catch::literals;
REQUIRE( performComputation() == 2.1_a );
Approx
的默认情况可以适配大多数情况,Catch 也提供了以下三种自定义方法:
Approx
's value before it is rejected.std::numeric_limits<float>::epsilon()*100
.Approx
's value before it is rejected.0.0
.Approx
for relative check.0.0
.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
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
期待无异常抛出的断言宏:
期待有异常抛出的断言宏:
期待有特定异常抛出的断言宏:
期待抛出一个转化为字符串后于提供字符串或字符匹配器匹配的异常 (匹配器在下边会有介绍):
REQUIRE_THROWS_WITH( openThePodBayDoors(), Contains( "afraid" ) && Contains( "can't do that" ) );
REQUIRE_THROWS_WITH( dismantleHal(), "My mind is going" );
期待抛出一个于匹配器匹配的异常:
请注意,THROW 断言系列期望传递单个表达式,而不是一个语句或一系列语句。 如果要检查更复杂的操作序列,可以使用 C++ 11 lambda函数。
REQUIRE_NOTHROW([&](){
int i = 1;
int j = 2;
auto k = i + j;
if (k == 3) {
throw 1;
}
}());
匹配器会在下边介绍到官方文档,这里主要描述匹配器断言宏:
匹配器可以使用 &&、|| 和 !逻辑操作。
当前 Catch 的断言是不支持线程安全的,详情请参考官方文档
由于预处理于编译不同的规则,造成断言宏在逗号分隔上会有问题,例如
REQUIRE_THROWS_AS(std::pair<int, int>(1, 2), std::invalid_argument);
会编译失败,因为预处理器看到提供了3个参数,但宏只接受2个参数。
使用 typedef 解决:
using int_pair = std::pair<int, int>;
REQUIRE_THROWS_AS(int_pair(1, 2), std::invalid_argument);
括号表达式(不通用,它可能需要在Catch方面进行额外更改才能正常工作):
TEST_CASE_METHOD((Fixture<int, int>), "foo", "[bar]") {
SUCCEED();
}
Matchers 是一种易于组合的断言形式,便于复杂的自定义类型配合使用。
Matchers 主要与 REQUIRE_THAT 或 CHECK_THAT 断言宏配合使用,并且匹配器之间还可以通过逻辑与或非组合。
using Catch::Matchers::EndsWith; // or Catch::EndsWith
std::string str = getStringFromSomewhere();
REQUIRE_THAT( str, EndsWith( "as a service" ) );
REQUIRE_THAT( str,
EndsWith( "as a service" ) ||
(StartsWith( "Big data" ) && !Contains( "web scale" ) ) ); // 组合
同时 Matchers 还支持多个参数,进而更加精准的断言,例如使用内置的字符串匹配器接受第二个参数标志位,指定是否区分大小写:
REQUIRE_THAT( str, EndsWith( "as a service", Catch::CaseSensitive::No ) );
注意,组合操作不会获取 Matchers 的所有权,也就是使用者必须要确保其声明周期:
TEST_CASE("Bugs, bugs, bugs", "[Bug]"){
std::string str = "Bugs as a service";
auto match_expression = Catch::EndsWith( "as a service" ) ||
(Catch::StartsWith( "Big data" ) && !Catch::Contains( "web scale" ) );
REQUIRE_THAT(str, match_expression);
}
Catch2 默认提供了一些 Matchers,它们都在 Catch::Matchers::foo 命名空间下,同样也可以导入到 Catch 命名空间。
内置的匹配器分为两种,一种是匹配器类型本身以及创建模板匹配器时提供模板参数推导的帮助器函数。例如,用于检查 std::vector 的两个实例是否相同的匹配器是 EqualsMatcher<T>
,但是希望用户改用 Equals 帮助器函数。
字符串匹配器包括 StartsWith
, EndsWith
, Contains
, Equals
, Matches
。前四个都是将指定字符串与结果进行特定规则匹配,而 Matches 则通过 ECMAScript 正则表达式匹配整个字符串。每个提供的 std::string 匹配器还带有一个可选的第二个参数,该参数决定是否区分大小写(默认情况下,它们区分大小写)。
Catch2 提供 5 个内置的匹配器针对 std::vector:
Contains
检查结果中是否存在指定变量。VectorContains
检查结果中是否存在指定的元素。Equals
它检查结果是否与特定向量完全相等(注意元素顺序)。UnorderedEquals
检查结果是否等于排列下的特定向量。Approx
它检查结果是否与特定向量“近似相等”(顺序很重要,但比较是通过“近似”完成的)。Catch2 提供 3 个内置的浮点匹配器
WithinAbsMatcher
接受目标一定距离内的浮点数。它使用 WithinAbs(double target, double margin)
枸橘函数构造。WithinUlpsMatcher
接受在目标的 ULP 一定范围内的浮点数。 因为对于浮点数和双精度数,ULP 比较需要以不同的方式进行,所以此匹配器的工具函数有两个重载,WithinULP(float target, int64_t ULPs)
、WithinULP(double target, int64_t ULPs)
。WithinRelMatcher
接受浮点数,该浮点数与目标数近似相等,并且具有特定的公差。 换句话说,它检查 |lhs - rhs| <= epsilon * max(|lhs|, |rhs|)
。四个工具构造函数 WithinRel(double target, double margin)
, WithinRel(float target, float margin)
, WithinRel(double target)
, WithinRel(float target)
。Catch 可以提供一些通用的匹配器:
REQUIRE_THAT("Hello olleH",
Predicate<std::string>(
[] (std::string const& str) -> bool { return str.front() == str.back(); },
"First and last character should be equal") //第二个参数是谓词的可选描述,仅在报告结果时使用。
);
Catch2 还提供了一个异常匹配器,可用于验证异常消息是否与所需字符串完全匹配。匹配器是 ExceptionMessageMatcher,我们还提供了一个辅助函数 Message。匹配的异常必须显示从 std::exception 派生,并且消息的匹配是完全正确的,包括大小写。
REQUIRE_THROWS_MATCHES(throwsDerivedException(), DerivedException, Message("DerivedException::what"));
Catch::MatcherBase<T>
派生出一个 Matcher 类,其中 T 是要测试的类型。构造函数接受并存储所需的任何参数(例如要与之进行比较的参数),然后重写两个方法:match() 和 describe()。// 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
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 override {
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 ) );
}
在测试期间可以记录并输出详细的数据信息,使用 INFO 可以实现记录打印的效果,但是要注意打印的相关变量有效作用域,并且如果在 INFO 记录之前就出现错误则不会上报。
TEST_CASE("Foo") {
INFO("Test case start");
for (int i = 0; i < 2; ++i) {
INFO("The number is " << i);
CHECK(i == 0);
}
}
//当 FOO 测试失败时,会输出当前记录的打印:
//Test case start
//The number is 1
TEST_CASE("Bar") {
INFO("Test case start");
for (int i = 0; i < 2; ++i) {
INFO("The number is " << i);
CHECK(i == i);
}
CHECK(false);
}
//当 Bar 测试失败时,只会输出打印:
//Test case start
//超过作用域的不会在上报
UNSCOPED_INFO
与 INFO
有两点不同:
void print_some_info() {
UNSCOPED_INFO("Info from helper");
}
TEST_CASE("Baz") {
print_some_info();
for (int i = 0; i < 2; ++i) {
UNSCOPED_INFO("The number is " << i);
}
CHECK(false);
}
// Baz 失败后输出打印:
// Info from helper
// The number is 0
// The number is 1
TEST_CASE("Qux") {
INFO("First info");
UNSCOPED_INFO("First unscoped info");
CHECK(false);
INFO("Second info");
UNSCOPED_INFO("Second unscoped info");
CHECK(false);
}
//当第一个 CHECK 失败后:
//First info
//First unscoped info
//到达第二个 CHECK 失败后:
//First info
//Second info
//Second unscoped info
所有的日志记录宏都可以向 c++ 的标准输入输出一样使用 << 进行数据流控制。
INFO( "The number is " << i );
CAPTURE( expression1, expression2, … ):有时,只需要记录变量或表达式的值。使用 CAPTURE 宏,该宏可以接受变量或表达式,并在捕获时打印出该变量/表达式及其值。
int a = 1, b = 2, c = 3;
CAPTURE( a, b, c, a + b, c > b, a == 1);
/*
a := 1
b := 2
c := 3
a + b := 3
c > b := true
a == 1 := true
*/
还可以捕获在括号(例如函数调用),方括号或花括号(例如初始值设定项)中使用逗号的表达式。 要正确捕获包含模板参数列表的表达式(换句话说,它包含尖括号之间的逗号),需要将该表达式括在括号内:CAPTURE( (std::pair<int, int>{1, 2}) );
Catch2 提供了用于单元测试的宏,同时还提供了一种特殊的测试方式节
:
test name 与 section name 字符串格式并且要唯一,tags 同样时字符串格式它标识一个或多个用方括号标记的标签。
标签允许任意数量的附加字符串与测试用例相关联。可以通过标签-甚至是组合了多个标签的表达式来选择(用于运行或仅用于列出)测试用例。 在最基本的层次上,它们提供了一种将几个相关测试组合在一起的简单方法。
TEST_CASE( "A", "[widget]" ) { /* ... */ }
TEST_CASE( "B", "[widget]" ) { /* ... */ }
TEST_CASE( "C", "[gadget]" ) { /* ... */ }
TEST_CASE( "D", "[widget][gadget]" ) { /* ... */ }
使用 "[widget]"
标签选中 A、B、D,"[gadget]"
标签选中 C、D,"[widget][gadget]"
标签仅选中 D。标记名称不区分大小写,并且支持任何 ASCII 字符串,包括空格 [tag with spaces]
和 [I said "good day"]
,都是可以被过滤的。
Catch2 保留所有以非字母数字字符开头的标签名称用于定义了许多“特殊”标签,这些标签对测试运行器本身具有意义,均以符号字符开头。 以下是当前定义的特殊标签及其含义的列表。
[.][integration]
,这样默认是不会运行当前测试用例,但是可以通过命令行指定 integration 标签来运行。[.][integration]
还可以缩写成 [.integration]
。[!mayfail]
,但如果通过则无法通过测试。如果您希望收到有关意外或第三方修复程序的通知,这将很有用。[#<filename>]
:使用 -# 或 --filenames-as-tags 运行 Catch2 会向所有包含的测试中添加以 # 开头的文件名(并去除所有扩展名)作为标签,例如 testfile.cpp 中的测试将全部标记为[#testfile]。[@<alias>]
:标签别名以 @ 开头。在标签表达式和通配测试名称(以及两者的组合)之间,可以构建非常复杂的模式来指示要运行哪些测试用例。 如果经常使用复杂模式,则能够为表达式创建别名很方便。 可以使用以下形式通过代码完成此操作:
CATCH_REGISTER_TAG_ALIAS( <alias string>, <tag expression> )
别名必须以@字符开头。 标签别名的示例是:
CATCH_REGISTER_TAG_ALIAS( "[@nhf]", "[failing]~[.]" )
当在命令行上使用 [@nhf] 时,它将匹配所有标记为 [failing] 的测试,但这些测试没有被隐藏。
行为驱动开发模式的编写测试用例:
SCENARIO( scenario name [, tags ] ):与 TEST_CASE 类似,只不过重新命名了,应该与 BDD 配套使用。
GIVEN( something )
WHEN( something )
THEN( something ):这三个宏都是映射到 SECTION 上的。
AND_GIVEN( something )
AND_WHEN( something )
AND_THEN( something ):与 上边三个宏类似,只不过命名特殊带有 AND。
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 );
}
}
}
}
除了TEST_CASE,Catch2 还支持按类型参数化的测试用例,形式为 TEMPLATE_TEST_CASE,TEMPLATE_PRODUCT_TEST_CASE 和 TEMPLATE_LIST_TEST_CASE。
测试名称和标签与 TEST_CASE 中的名称和标签完全相同,不同之处在于必须提供标签字符串(但是可以为空)。 从 type1 到 typen 是该测试用例应运行的类型的列表,并且在测试代码中,当前类型可用作 TestType 类型。
由于C ++预处理程序的限制,如果要指定具有多个模板参数的类型,则需要将其括在括号中
std::map<int, std::string>
needs to be passed as(std::map<int, std::string>)
。
TEMPLATE_TEST_CASE( "vectors can be sized and resized", "[vector][template]", int, std::string, (std::tuple<int,float>) ) {
std::vector<TestType> 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( "We can use the 'swap trick' to reset the capacity" ) {
std::vector<TestType> empty;
empty.swap( v );
REQUIRE( v.capacity() == 0 );
}
}
SECTION( "reserving smaller does not change size or capacity" ) {
v.reserve( 0 );
REQUIRE( v.size() == 5 );
REQUIRE( v.capacity() >= 5 );
}
}
template-type1 到 template-typen 是模板模板类型的列表,应该与 template-arg1 到 template-argm 中的每一个结合使用,从而产生 n * m 个测试用例。在测试用例中,使用 TestType 表示不同的类型。
要将一种以上类型指定为单个 template-type 或 template-arg,您必须将这些类型括在另外的括号中,例如 ((int, float), (char, double))
指定2个模板参数,每个模板参数由2种具体类型(分别为int,float 和 char,double)组成。 如果仅指定一种类型作为模板类型或 template-args 的完整集合,则也可以省略括号的外部集合。
template< typename T>
struct Foo {
size_t size() {
return 0;
}
};
TEMPLATE_PRODUCT_TEST_CASE("A Template product test case", "[template][product]", (std::vector, Foo), (int, float)) {
TestType x;
REQUIRE(x.size() == 0);
}
TEMPLATE_PRODUCT_TEST_CASE("Product with differing arities", "[template][product]", std::tuple, (int, (int, double), (int, double, float))) {
TestType x;
REQUIRE(std::tuple_size<TestType>::value >= 1);
}
虽然在单个 TEMPLATE_TEST_CASE 或 TEMPLATE_PRODUCT_TEST_CASE 中指定的类型数量有上限,但该限制非常高,在实践中不应遇到。
类型列表是应在其上实例化测试用例的类型的通用列表。 列表可以是 std::tuple,boost::mpl::list,boost::mp11::mp_list 或带有模板 <typename ...>
签名的任何内容。可以在多个测试用例中重用类型列表。
using MyTypes = std::tuple<int, char, float>;
TEMPLATE_LIST_TEST_CASE("Template test case with test types specified inside std::tuple", "[template][list]", MyTypes)
{
REQUIRE(sizeof(TestType) > 0);
}
除了类型参数化的测试用例之外,Catch2 还支持签名基本参数化的测试用例,形式为 TEMPLATE_TEST_CASE_SIG 和 TEMPLATE_PRODUCT_TEST_CASE_SIG。 这些测试用例具有类似的语法,例如类型参数化的测试用例,并带有一个附加的位置参数来指定签名。
Signature:签名有一些严格的规则,遵循这些测试用例才能正常工作:
typename T, size_t S
在测试用例声明中必须具有此格式 ((typename T, size_t S), T, S)
。typename T,size_t S,typename ... Ts
在测试用例声明中必须具有此格式((typename T, size_t S, typename...Ts), T, S, Ts...)
。int V
在测试用例声明中必须具有此格式 ((int V), V)
。TEMPLATE_TEST_CASE_SIG( test name , tags, signature, type1, type2, …, typen ):在 TEMPLATE_TEST_CASE_SIG 测试用例中,可以使用签名中定义的模板参数的名称。
TEMPLATE_TEST_CASE_SIG("TemplateTestSig: arrays can be created from NTTP arguments", "[vector][template][nttp]",
((typename T, int V), T, V), (int,5), (float,4), (std::string,15), ((std::tuple<int, float>), 6)) {
std::array<T, V> v;
REQUIRE(v.size() > 1);
}
template<typename T, size_t S>
struct Bar {
size_t size() { return S; }
};
TEMPLATE_PRODUCT_TEST_CASE_SIG("A Template product test case with array signature", "[template][product][nttp]", ((typename T, size_t S), T, S), (std::array, Bar), ((int, 9), (float, 42))) {
TestType x;
REQUIRE(x.size() > 0);
}