当前位置: 首页 > 工具软件 > Catch2 > 使用案例 >

c++单元测试框架Catch2的简单使用

白丁雨
2023-12-01

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起名字,或者加标签:

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)

SECTION

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

Matchers顾名思义就是某个string或其它类型是否和Matcher所定义的条件匹配。

String matchers

内置的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

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++单元测试框架

 类似资料: