CPP测试框架-Catch2简介

2,480 阅读3分钟

1.简介

catch是一个C++多范式测试框架,以头文件形式提供支持

主要特性:

  1. 头文件方式支持,无其他库依赖
  2. 支持自注册功能/函数
  3. 支持BDD-style
  4. 测试用例相互隔离
  5. 支持标准C++比较操作符
  6. 支持标记分组
  7. 可测试自己

2.使用

2.1 基本

TEST_CASE( test name [tags] )

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
TEST_CASE( "vectors can be sized and resized", "[vector]") {
    std::vector<int> v( 5 );
    REQUIRE( v.size() == 5 );
    REQUIRE( v.capacity() >= 5 );
}

支持自定义main,添加 CATCH_CONFIG_MAIN 宏即可
如果需要使用自定义的main函数,添加CATCH_CONFIG_RUNNER 宏,在完成必要的逻辑后,添加 return Catch::Session().run(argc, argv);

2.1.1 断言宏

REQUIRE( expression )
CHECK( expression )
两者区别:CHECK 失败后继续执行当前test case
针对 CHECK(a == 1 && b == 2) 情况,需要在表达式两边添加() => CHECK((a == 1 && b == 2)),否则会编译不通过

2.1.2 浮点对比

catch 利用 Approx 类用于浮点数的判断
Approx 重载了比较操作符,可直接使用比较操作符,进行判断。
Approx 提供了3种方法:

  • epsilon 相对于原值的百分比值偏差,比如epsilon=0.01,表示与其比较的值在Approx值的%1的范围均为相等
  • margin 绝对偏差数值
  • scale 偏差范围:(Approx::scale + Approx::value)* epsilon,scale对margin无影响
//epsilon
Approx targete = Approx(100).epsilon(0.01);
REQUIRE(100.0 == targete); //true
REQUIRE(200.0 == targete); //false

//margin
Approx targetm = Approx(100).margin(5);
REQUIRE(100.0 == targetm); //true
REQUIRE(200.0 == targetm);  //false
  
//scale
Approx targets = Approx(100).scale(100).epsilon(0.01);
REQUIRE(100.7 == targets); // true
2.1.3 Matchers

执行类型是否和Matcher所定义的条件match,目前支持string、vector、Floating point、Generic、Exception Maters,支持自定义matcher。
通过REQUIRE_THAT() CHECK_THAT() 宏引入,支持2个参数,第一个参数为被测试对象/值,第二个为matcher表达式,支持多个表达式 &&, || or !

详细方法:https://github.com/catchorg/Catch2/blob/master/docs/matchers.md#top

Demo

TEST_CASE( "matcher test", "[hello]" ) {
    REQUIRE_THAT( str,
        EndsWith( "hello world" ) ||
        (StartsWith( "Daniel" ) && !Contains( "very good" ) ) );
}

自定义Matchers,继承Catch::MatcherBase<T>,实现match()、describe()函数即可

class IntRange : public Catch::MatcherBase<int> {
    int m_begin, m_end;
public:
    IntRange( int begin, int end ) : m_begin( begin ), m_end( end ) {}

    bool match( int const& i ) const override {
        return i >= m_begin && i <= m_end;
    }

    virtual std::string describe() const override {
        std::ostringstream ss;
        ss << "is between " << m_begin << " and " << m_end;
        return ss.str();
    }
};

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 ) );
}

2.2 section

Test case支持多个sections的创建,sections前后可进行setup teardown管理。每个section都是独立的单元,每个section都是从test case的最开始运行,section串行执行。支持嵌套section,当前section执行失败后,嵌套的section将不会执行

Demo

TEST_CASE( "vectors sized and resized", "[vector]" ) {
    //setup
    std::vector<int> v( 5 );
    REQUIRE( v.size() == 5 );
    REQUIRE( v.capacity() >= 5 );

    SECTION( "resizing size and capacity" ) {
        v.resize( 10 );

        REQUIRE( v.size() == 10 );
        REQUIRE( v.capacity() >= 10 );
    }
    SECTION( "resizing size but not capacity" ) {
        v.resize( 0 );

        REQUIRE( v.size() == 0 );
        REQUIRE( v.capacity() >= 5 );
        
        SECTION( "reserving again does not change capacity" ) {
            v.reserve( 7 );

            REQUIRE( v.capacity() >= 10 );
        }
    }
    
    //teardown
}

2.3 BDD-style

BDD-行为驱动开发

Demo

SCENARIO( "vectors sized and resized", "[vector]" ) {

    GIVEN( "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( "size changes but not capacity" ) {
                REQUIRE( v.size() == 0 );
                REQUIRE( v.capacity() >= 5 );
            }
        }
    }
}

3.其他

3.1 日志

  • UNSCOPED -- 临时,保留check false最新前一条日志,其余被覆盖, 成功的 case 不打印日志
  • INFO -- 成功的 case 不打印日志
  • WARN -- 一直显示,不中断 case
  • FAIL -- 发生时,中断 case
  • FAIL_CHECK -- 同fail,不中断 case

支持streaming流式操作
WARN/WARN/FAIL_CHECK发生时,INFO日志不打印

3.2 Test fixtures

  • TEST_CASE_METHOD 提供通过继承方式,实现对私有/保护函数的测试

demo

class DBConnection {
public:
    bool executeSQL(const std::string& str, const int& str1, const std::string& str2){
        return true;
    }
};

class UniqueTestsFixture {
private:
    static int uniqueID;
protected:
    DBConnection conn;
protected:
    int getID() {
        return ++uniqueID;
    }
};

int UniqueTestsFixture::uniqueID = 0;

TEST_CASE_METHOD(UniqueTestsFixture, "Create Employee/No Name", "[hello]") {
    REQUIRE_THROWS(conn.executeSQL("INSERT INTO employee (id, name) VALUES (?, ?)", getID(), ""));
}
TEST_CASE_METHOD(UniqueTestsFixture, "Create Employee/Normal", "[world]") {
    REQUIRE(conn.executeSQL("INSERT INTO employee (id, name) VALUES (?, ?)", getID(), "Joe Bloggs"));
}

模板测试,要求tag非空,如果有多个模板参数,需要括号包围

  • TEMPLATE_TEST_CASE_METHOD 模板测试
    格式:
    TEMPLATE_TEST_CASE/_METHOD ( test name , tags, type1, type2, ..., typen )
  • TEMPLATE_PRODUCT_TEST_CASE_METHOD 模板模板测试
    格式:
    TEMPLATE_PRODUCT_TEST_CASE/_METHOD ( test name , tags, (template-type1, template-type2, ..., template-typen), (template-arg1, template-arg2, ..., template-argm) )

链接:
https://github.com/catchorg/Catch2/blob/67b4ada6b0fbe98368df934e1378aeae1ba7f235/docs/test-fixtures.md
https://github.com/catchorg/Catch2/blob/67b4ada6b0fbe98368df934e1378aeae1ba7f235/docs/test-cases-and-sections.md

3.3 Event Listeners

通过继承Catch::TestEventListenerBase,以实现用例/section/Assertions的hook

4.备注

  • 多Test文件情况下,CATCH_CONFIG_MAIN 仅需定义一次
  • github地址:https://github.com/catchorg/Catch2