Catch2是一个简单的c++单元测试框架,v2版本基于c++11开发,v3版本需要c++14及以上,最初版本Catch1.x基于c++98。
项目地址是 Catch2 或 镜像。
Catch2简单易用,v2版本只需要下载catch.hpp,先选择v2.x分支,路径是single_include/catch2/catch.hpp,主分支当前是v3版本,将extras目录下的catch_amalgamated.hpp和catch_amalgamated.cpp下载,包含到你的工程就可以了。
Catch2不依赖外部库,只下载上面说到的几个文件即可编译。
以下例子中均使用Catch2 v2.13.7。
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
int sum1(int num)
{
int sum = 0;
for(; num; --num)
sum += num;
return sum;
}
int sum2(int num)
{
return num*(1+num)/2;
}
TEST_CASE() {
REQUIRE(sum1(2) == sum2(2));
}
编译时间有点久。
第一行的作用是由catch提供一个main函数:
// Standard C/C++ main entry point
int main (int argc, char * argv[]) {
return Catch::Session().run( argc, argv );
}
或者可以自己写:
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
int main (int argc, char * argv[]) {
return Catch::Session().run(argc, argv);
}
输出结果是:
===============================================================================
All tests passed (1 assertion in 1 test case)
可以为TEST_CASE起名字,或者加标签:
TEST_CASE("case 1", "[tag 1][tag2]") {
REQUIRE(Factorial(2) == 2);
}
TEST_CASE("case2", "[tag2]") {
REQUIRE(sum1(20) == sum2(20));
}
“case 1”是test case的名字,全局必须唯一(可以有多个匿名test case), “tag 1”,"tag2"是标签名,需要放在[]内部。一个test case可以有多个标签,多个test case可以使用相同的标签。
REQUIRE是一个assert宏,用来判断是否相等。另外还有CHECK宏,与REQUIRE不同的是,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)
这样的语句,由于宏展开的原因,catch不能通过编译,需要在表达式的两端加上括号CHECK((a == 1 && b == 2))
。
一些常用命令行选项:
test.exe -l
All available test cases:
case 1
[tag 1][tag2]
case2
[tag2]
2 test cases
test.exe -t
All available tags:
1 [tag 1]
2 [tag2]
2 tags
test.exe "case2"
Filters: case2
===============================================================================
All tests passed (1 assertion in 1 test case)
test.exe [tag2]
Filters: [tag2]
===============================================================================
All tests passed (2 assertions in 2 test cases)
Catch提供了一种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.exe is a Catch v2.13.7 host application.
Run with -? for options
-------------------------------------------------------------------------------
vectors can be sized and resized
-------------------------------------------------------------------------------
test.cpp:20
...............................................................................
test.cpp:23: FAILED:
CHECK( v.empty() )
with expansion:
false
-------------------------------------------------------------------------------
vectors can be sized and resized
-------------------------------------------------------------------------------
test.cpp:20
...............................................................................
test.cpp:23: FAILED:
CHECK( v.empty() )
with expansion:
false
-------------------------------------------------------------------------------
vectors can be sized and resized
-------------------------------------------------------------------------------
test.cpp:20
...............................................................................
test.cpp:23: FAILED:
CHECK( v.empty() )
with expansion:
false
-------------------------------------------------------------------------------
vectors can be sized and resized
-------------------------------------------------------------------------------
test.cpp:20
...............................................................................
test.cpp:23: FAILED:
CHECK( v.empty() )
with expansion:
false
===============================================================================
test cases: 1 | 0 passed | 1 failed
assertions: 16 | 12 passed | 4 failed
可以看到test case执行了4次。
catch2浮点数比较采用类Approx,相关代码如下:
double m_epsilon;
double m_margin;
double m_scale;
double m_value;
Approx::Approx ( double value )
: m_epsilon( std::numeric_limits<float>::epsilon()*100 ),
m_margin( 0.0 ),
m_scale( 0.0 ),
m_value( 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);
}
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)));
}
举个例子:
Approx target = Approx(100).epsilon(0.01).margin(0.01);
CHECK(100 == target);
CHECK(100.5 == target);
CHECK(108 == Approx(100).epsilon(0.1).margin(0.01));
Catch2为用户定义了一个替代字符_a,这样使用时不用每次都写Approx,此时只能使用默认参数:
namespace literals {
Detail::Approx operator "" _a(long double val) {
return Detail::Approx(val);
}
Detail::Approx operator "" _a(unsigned long long val) {
return Detail::Approx(val);
}
} // end namespace literals
举个例子:
using namespace Catch::literals;
REQUIRE( 2.1 == 2.1_a );
Matchers顾名思义就是某个string或其它类型是否和Matcher所定义的条件匹配。
内置的string matchers有Equals,Contains,EndsWith,StartsWith,Matches,前四个是常见的string或substring的比较,Matches是ECMAScript类型的正则表达式:
namespace Catch {
namespace Matchers {
namespace StdString {
//......
} // namespace StdString
// The following functions create the actual matcher objects.
// This allows the types to be inferred
StdString::EqualsMatcher Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
StdString::ContainsMatcher Contains( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
StdString::EndsWithMatcher EndsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
StdString::StartsWithMatcher StartsWith( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
StdString::RegexMatcher Matches( std::string const& regex, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes );
} // namespace Matchers
} // namespace Catch
举个例子:
using namespace Catch::Matchers;
CHECK_THAT("hello world", EndsWith("world" ) && !StartsWith("hello"));
REQUIRE_THAT("hello new world", EndsWith("world" ) && StartsWith("hello"));
vector类型的matchers有Contains,VectorContains,Equals,Approx,UnorderedEquals。
Contains判断某个vector是否包含另外一个vector,VectorContains判断某个vector是否有某个元素,Equals判断两个vector是否相同,UnorderedEquals判断两个vector的元素集合是否相同。
ApproxMatcher类内部有个成员变量Approx approx
,并有epsilon(),margin(),scale()方法,用于判断两个vector,每个对应位置的元素,用Approx类处理后比较是否相同。
namespace Catch {
namespace Matchers {
namespace Vector {
//......
} // namespace Vector
// The following functions create the actual matcher objects.
// This allows the types to be inferred
template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>
Vector::ContainsMatcher<T, AllocComp, AllocMatch> Contains( std::vector<T, AllocComp> const& comparator ) {
return Vector::ContainsMatcher<T, AllocComp, AllocMatch>( comparator );
}
template<typename T, typename Alloc = std::allocator<T>>
Vector::ContainsElementMatcher<T, Alloc> VectorContains( T const& comparator ) {
return Vector::ContainsElementMatcher<T, Alloc>( comparator );
}
template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>
Vector::EqualsMatcher<T, AllocComp, AllocMatch> Equals( std::vector<T, AllocComp> const& comparator ) {
return Vector::EqualsMatcher<T, AllocComp, AllocMatch>( comparator );
}
template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>
Vector::ApproxMatcher<T, AllocComp, AllocMatch> Approx( std::vector<T, AllocComp> const& comparator ) {
return Vector::ApproxMatcher<T, AllocComp, AllocMatch>( comparator );
}
template<typename T, typename AllocComp = std::allocator<T>, typename AllocMatch = AllocComp>
Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch> UnorderedEquals(std::vector<T, AllocComp> const& target) {
return Vector::UnorderedEqualsMatcher<T, AllocComp, AllocMatch>( target );
}
} // namespace Matchers
} // namespace Catch
项目自带的例子(from Catch2/tests/SelfTest/UsageTests/Matchers.tests.cpp):
template <typename T> struct CustomAllocator : private std::allocator<T> {
using size_type = size_t;
using difference_type = ptrdiff_t;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using value_type = T;
template <typename U> struct rebind { using other = CustomAllocator<U>; };
using propagate_on_container_move_assignment = std::true_type;
using is_always_equal = std::true_type;
CustomAllocator() = default;
CustomAllocator( const CustomAllocator& other ):
std::allocator<T>( other ) {}
template <typename U> CustomAllocator( const CustomAllocator<U>& ) {}
~CustomAllocator() = default;
using std::allocator<T>::allocate;
using std::allocator<T>::deallocate;
};
TEST_CASE( "Vector matchers", "[matchers][vector]" ) {
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<double> v3;
v3.push_back( 1 );
v3.push_back( 2 );
v3.push_back( 3 );
std::vector<double> v4;
v4.push_back( 1 + 1e-8 );
v4.push_back( 2 + 1e-8 );
v4.push_back( 3 + 1e-8 );
std::vector<int, CustomAllocator<int>> v5;
v5.push_back( 1 );
v5.push_back( 2 );
v5.push_back( 3 );
std::vector<int, CustomAllocator<int>> v6;
v6.push_back( 1 );
v6.push_back( 2 );
std::vector<int> empty;
using namespace Catch::Matchers;
SECTION( "Contains (element)" ) {
CHECK_THAT( v, VectorContains( 1 ) );
CHECK_THAT( v, VectorContains( 2 ) );
CHECK_THAT( v5, ( VectorContains<int, CustomAllocator<int>>( 2 ) ) );
}
SECTION( "Contains (vector)" ) {
CHECK_THAT( v, Contains( v2 ) );
CHECK_THAT( v, Contains<int>( { 1, 2 } ) );
CHECK_THAT( v5,
( Contains<int, std::allocator<int>, CustomAllocator<int>>(
v2 ) ) );
v2.push_back( 3 ); // now exactly matches
CHECK_THAT( v, Contains( v2 ) );
CHECK_THAT( v, Contains( empty ) );
CHECK_THAT( empty, Contains( empty ) );
CHECK_THAT( v5,
( Contains<int, std::allocator<int>, CustomAllocator<int>>(
v2 ) ) );
CHECK_THAT( v5, Contains( v6 ) );
}
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
CHECK_THAT( v, Equals<int>( { 1, 2, 3 } ) );
v2.push_back( 3 );
CHECK_THAT( v, Equals( v2 ) );
CHECK_THAT(
v5,
( Equals<int, std::allocator<int>, CustomAllocator<int>>( v2 ) ) );
v6.push_back( 3 );
CHECK_THAT( v5, Equals( v6 ) );
}
SECTION( "UnorderedEquals" ) {
CHECK_THAT( v, UnorderedEquals( v ) );
CHECK_THAT( v, UnorderedEquals<int>( { 3, 2, 1 } ) );
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 ) );
CHECK_THAT(
v5,
( UnorderedEquals<int, std::allocator<int>, CustomAllocator<int>>(
permuted ) ) );
auto v5_permuted = v5;
std::next_permutation( begin( v5_permuted ), end( v5_permuted ) );
CHECK_THAT( v5_permuted, UnorderedEquals( v5 ) );
}
}