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 ) ); } }
catch2:一个好用的C++单元测试框架