Catch2单元测试框架

一、Catch简介

Catch是一个很时尚的,C++原生的框架,只包含一个头文件,用于单元测试,TDD测试驱动开发和BDD行为驱动开发。
在catch的文档指出,对于C++单元测试框架,目前已经有 Google Test, Boost.Test, CppUnit, Cute, 以及其它的一些,相比较之下catch简单易用、不依赖外部库、支持BDD、测试命名自由等优势。
Catch是一个header-only的开源库,这意味着你只需要把一个头文件放到系统或者你的工程的某个目录,编译的时候指向它就可以了。

二、Catch使用

头文件

github地址:https://github.com/philsquared/Catch

$ git clone https://github.com/philsquared/Catch.git

可以在github直接下载catch.hpp文件,引入到自己的c++工程中。

#include <catch2/catch.hpp>
使用
TEST_CASE() {
    REQUIRE(2 == 2);
}

当然也可以为TEST_CASE起名字,或者加标签:

// test case的名字,全局必须唯一
// "tag1"是标签名,需要放在[]内部。一个test case可以有多个标签,多个test case可以使用相同的标签。
TEST_CASE("Test_name", "[tag1]") {
    REQUIRE(2 == 2); //REQUIRE是一个assert宏,用来判断是否相等。
}

官网用例:

#define CATCH_CONFIG_MAIN  // This tells Catch to provide a main() - only do this in one cpp file
#include "catch.hpp"

unsigned int Factorial( unsigned int number ) {
    return number <= 1 ? number : Factorial(number-1)*number;
}

TEST_CASE( "Factorials are computed", "[factorial]" ) {
    REQUIRE( Factorial(1) == 1 );
    REQUIRE( Factorial(2) == 2 );
    REQUIRE( Factorial(3) == 6 );
    REQUIRE( Factorial(10) == 3628800 );
}

三、基本断言

REQUIRE(expression)
CHECK(expression)
REQUIRE_FALSE(expression)
CHECK_FALSE(expression)
注意:REQUIRE和CHECK最主要的区别在于REQUIRE表达式为false时中断执行,而CHECK继续执行。

Matcher比较器

REQUIRE_THAT(lhs, matcher expression)
CHECK_THAT(lhs, matcher expression)
主要内置Matchers
– string matchers:StartsWith, EndsWith, Contains, Equals and Matches
– vector matchers:Contains, VectorContains and Equals
– floating point matchers:WithinULP and WithinAbs
REQUIRE_THAT( str, EndsWith( "as a service", Catch::CaseSensitive::No ) );

浮点数比较

//浮点数比较:
//    epsilon:default std::numeric_limits::epsilon()*100.
//    margin:default 0.0.
//    scale:default 0.0.
TEST_CASE("approx epsilon", "[single-file]")
{
    // 闭区间
    // [100-100*epsilon,100+100*epsilon]
    Approx target = Approx(100).epsilon(0.01);
​
    CHECK(100.0 == target); // Obviously true
    CHECK(99.0 == target); // Obviously true
//    CHECK_FALSE(98.1 == target); // Obviously true
    CHECK_FALSE(98.1 == target); // Obviously true
    CHECK(101.0 == target); // Obviously true
//    CHECK_FALSE(101.1 == target); // Obviously true
    CHECK_FALSE(101.1 == target); // Obviously true
}
TEST_CASE("approx margin", "[single-file]")
{
    // 闭区间
    // [100-margin,100+margin]
    Approx target = Approx(100).margin(1);
​
    CHECK(100.0 == target); // Obviously true
    CHECK(99.0 == target); // Obviously true
    CHECK_FALSE(98.1 == target); // Obviously true
    CHECK(101.0 == target); // Obviously true
    CHECK_FALSE(101.1 == target); // Obviously true
}
信息输出:Logging
    INFO( message expression )
    WARN( message expression )
    FAIL( message expression )
    FAIL_CHECK( message expression )
    CAPTURE( expression1, expression2, … )

四、命令行参数 - TAG

Catch 提供的这个 main 函数实现的另一个强大的功能是丰富的命令行参数,你可以选择执行其中的某些 TEST_CASE,也可以选择不执行其中的某些 TEST_CASE,你可以用它调整输出到 xml 文件,也可以用它从文件中读取需要测试的用例。

需要注意的是,这些强大的命令行大多数是基于 TAG 的,也就是 TEST_CASE 定义中的第二个参数。

TEST_CASE( "vectors can be sized and resized", "[vector]" )

上面的定义中 “[vector]” 就是一个 TAG,你可以提供多个 TAG:

TEST_CASE( "D", "[widget][gadget]" ) { /* ... */ }

这样的话你可以在命令行中根据 TAG 去选择是否需要执行该 TEST_CASE。比如:

./catch "[vector]" // 只执行那些标记为 vector 的测试用例

此外你还可以使用一些特殊的字符,比如 [.] 表示隐藏。[.integration] 则表示默认隐藏,但是可以在命令行中使用 [.integration] 这个 TAG 执行。

./catch // 默认不执行 integration
./catch "[.integration]" // 使用 TAG 执行 integration

例:

$ shtf_sdk_interface_catchtest_ [FaceExtractFeature] -c ErrordataTest --use-colour yes -r xml -d yes --order lex

五、我自己学习的时候写的例程:

ps:含有丰富的注释,以供理解学习。

//
// Created by toson on 19-3-13.
//
// This file is a sample file.
//
#include <iostream>
#include <cstdlib>
#include <map>

#define CATCH_CONFIG_MAIN
#include "../third/catch.hpp"

using namespace std;

//#include "Trie.h"
typedef struct TrieNode {
    bool completed;
    std::map<char, shared_ptr<TrieNode>> children;
    TrieNode() : completed(false) {};
} TrieNode;

class Trie {
public:
    Trie(void){
        root = shared_ptr<TrieNode>(new TrieNode);
    };
    ~Trie(void){};
    bool insert(std::string word);
    bool search(std::string word);
private:
    shared_ptr<TrieNode> root;
};

bool Trie::insert(std::string word) {
    int i=0;
    shared_ptr<TrieNode> roo;
    auto itr = root->children.find(word[0]);
    if(itr == root->children.end()){
        root->children[word[0]] = shared_ptr<TrieNode>(new TrieNode);
        roo = root->children[word[0]];
    } else{
        roo = itr->second;
    }
    for(i=1; i<word.length(); i++){
        itr = roo->children.find(word[i]);
        if(itr == roo->children.end()){
            roo->children[word[i]] = shared_ptr<TrieNode>(new TrieNode);
            roo = roo->children[word[i]];
        } else {
            roo = itr->second;
        }
    }
    if(i == word.length()){
        roo->completed = true;
        return true;
    }
    return false;
}

bool Trie::search(std::string word) {
    int i=0;
    shared_ptr<TrieNode> roo;
    auto itr = root->children.find(word[0]);
    if(itr == root->children.end()){
        return false;
    } else{
        roo = itr->second;
    }
    for(i=1; i<word.length(); i++){
        itr = roo->children.find(word[i]);
        if(itr == roo->children.end()){
            return false;
        } else {
            roo = itr->second;
        }
    }
    if(roo->completed){
        return true;
    }
    return false;
}

// ”Testing Trie"是test case的名字,全局必须唯一, "tag1"是标签名,需要放在[]内部。
// 一个test case可以有多个标签("[tag1]"),多个test case可以使用相同的标签。 //标签:可用于选择进行测试。
TEST_CASE("Testing Trie", "[tag1]") {
//    std::cout << __FUNCTION__ << endl;
    // set up
    Trie *t = new Trie();
//    std::cout << "SetUp" << endl;

    // different sections
    SECTION("Search an existent word.") {
//        std::cout << "Test: Search an existent word." << endl;
        string word = "abandon";
        t->insert(word);
        REQUIRE(t->search(word)); //REQUIRE是一个assert宏,用来判断是否相等。REQUIRE表达式为false时中断执行
        word = "abando";
        CHECK_FALSE(t->search(word)); //CHECK表达式为false时继续执行。
        t->insert(word);
        REQUIRE(t->search(word));
        word = "toson";
        REQUIRE_FALSE(t->search(word)); // REQUIRE_FALSE(t->search(word))  // CHECK_FALSE(t->search(word))
        t->insert(word);
        REQUIRE(t->search(word));
        word = "abando";
        REQUIRE(t->search(word));
    }
    SECTION("Search a nonexistent word.") {
//        std::cout << "Test: Search an nonexistent word." << endl;
        string word = "abandon2";
        REQUIRE_FALSE(t->search(word));
    }

    // tear down
    delete t;
//    std::cout << "TearDown" << endl;
}

// 断言方法:
//    REQUIRE_NOTHROW( expression ) and
//    CHECK_NOTHROW( expression )
//Expects that no exception is thrown during evaluation of the expression.
//
//    REQUIRE_THROWS( expression ) and
//    CHECK_THROWS( expression )
//Expects that an exception (of any type) is be thrown during evaluation of the expression.
//
//    REQUIRE_THROWS_AS( expression, exception type ) and
//    CHECK_THROWS_AS( expression, exception type )
//Expects that an exception of the specified type is thrown during evaluation of the expression. Note that the exception type is extended with const& and you should not include it yourself.
//
//    REQUIRE_THROWS_WITH( expression, string or string matcher ) and
//    CHECK_THROWS_WITH( expression, string or string matcher )
//Expects that an exception is thrown that, when converted to a string, matches the string or string matcher provided (see next section for Matchers).
//
//    REQUIRE_THROWS_MATCHES( expression, exception type, matcher for given exception type ) and
//    CHECK_THROWS_MATCHES( expression, exception type, matcher for given exception type )
//Expects that exception of exception type is thrown and it matches provided matcher (see next section for Matchers).


//浮点数比较:
//    epsilon:default std::numeric_limits::epsilon()*100.
//    margin:default 0.0.
//    scale:default 0.0.
TEST_CASE("approx test", "[single-file]")
{
    // 闭区间
    // [100-100*epsilon,100+100*epsilon]
    Approx target = Approx(100).epsilon(0.01);

    SECTION("approx epsilon [single-file]") {
//        INFO("approx epsilon [single-file]");
        CHECK(100.0 == target); // Obviously true
        CHECK(99.0 == target); // Obviously true
        CHECK_FALSE(98.1 == target); // Obviously true
        CHECK(101.0 == target); // Obviously true
        CHECK_FALSE(101.1 == target); // Obviously true
    }

    // 闭区间
    // [100-margin,100+margin]
    Approx target2 = Approx(100).margin(1);

    SECTION("approx margin [single-file]") {
//        INFO("approx margin: INFO");
//        CAPTURE("approx margin: CAPTURE");
        CHECK(100.0 == target2); // Obviously true
        CHECK(99.0 == target2); // Obviously true
        CHECK_FALSE(98.1 == target2); // Obviously true
//        CHECK_THROWS_WITH(98.1 == target2, "CHECK_THROWS_WITH");
        CHECK(101.0 == target2); // Obviously true
        CHECK_FALSE(101.1 == target2); // Obviously true

//        REQUIRE_NOTHROW([&](){
//            int i = 1;
//            int j = 2;
//            auto k = i + j;
//            if (k == 3) {
//                throw 1;
//            }
//        }());
    }

    //TearDown
}



//主要内置Matchers: CHECK_THAT, REQUIRE_THAT
//  – string matchers:StartsWith, EndsWith, Contains, Equals and Matches
//  – vector matchers:Contains, VectorContains and Equals
//  – floating point matchers:WithinULP and WithinAbs
using Catch::Matchers::EndsWith; // or Catch::EndsWith
using Catch::Matchers::StartsWith;
using Catch::Matchers::Contains; //包含
using Catch::Predicate; //包含
TEST_CASE("string matchers", "[single-file]") {
    //例如,要断言字符串以某个子字符串结尾,请执行以下操作:
    string str = "Big data abcweb scale as a service";
    CHECK_THAT( str, EndsWith( "as a Service", Catch::CaseSensitive::No ) ); //CaseSensitive:默认区分大小写
    CHECK_THAT( str,
                  EndsWith( "as a service" ) ||
                  (StartsWith( "Big data" ) && !Contains( "web scale" ) ) );
    //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")
    );
//    using int_pair = std::pair<int, int>;
//    REQUIRE_THROWS_AS(int_pair(1, 2), std::invalid_argument);
}

//TEST_CASE_METHOD((Fixture<int, int>), "foo", "[bar]") {
//    SUCCEED();
//}


//Logging
//    INFO( message expression )
//    WARN( message expression )
//    FAIL( message expression )
//    FAIL_CHECK( message expression )
//    CAPTURE( expression1, expression2, … )
TEST_CASE("Logging test", "[logging]"){
    int a = 0, b = 2, c = 3;

    SECTION("Logging test [logging]") {
        INFO("Logging test: INFO");
//        WARN("Logging test: WARN");
//        WARN(a);
//        FAIL("Logging test: FAIL"); //FAIL表达式为false时将中断执行。
//        FAIL_CHECK("Logging test: FAIL_CHECK"); //FAIL_CHECK表达式为false时继续执行。
        CAPTURE("Logging test: CAPTURE");
        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}));
//        CHECK(false);
    }
}

TEST_CASE("Foo") {
    INFO("Test case start");
    for (int i = 0; i < 2; ++i) {
        INFO("The number is " << i);
//        CHECK(i == 0);
    }
}

TEST_CASE("Bar") {
    INFO("Test case start");
    for (int i = 0; i < 2; ++i) {
        INFO("The number is " << i);
        CHECK(i == i);
    }
//    CHECK(false); //调试时可开启,将直接产生FAILED
}

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

TEST_CASE("Qux") {
    INFO("First info"); //INFO将持续到结束
    UNSCOPED_INFO("First unscoped info"); //UNSCOPED_INFO在一次检查后会清除
//    CHECK(false);

    INFO("Second info");
    UNSCOPED_INFO("Second unscoped info");
//    CHECK(false);
}


/*---------------------------BDD(行为驱动开发)-----------------------------*/
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 );
            }
        }
    }
}
/*--------------------------------------------------------*/

class DBConnection{ //自己写的
public:
    DBConnection(int in){};
    static int createConnection(string str){return 0;};
    int executeSQL(string str, int id, string s){return 0;};
};

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

int UniqueTestsFixture::uniqueID = 0;

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


/*--------------------------------------------------------------------------------*/
//Catch2还提供TEMPLATE_TEST_CASE_METHOD和TEMPLATE_PRODUCT_TEST_CASE_METHOD
// 它们可以与模板化夹具和模板化模板夹具一起使用,以执行多种不同类型的测试。
template< typename T >
struct Template_Fixture {
    Template_Fixture(): m_a(1) {}

    T m_a;
};

TEMPLATE_TEST_CASE_METHOD(Template_Fixture,"A TEMPLATE_TEST_CASE_METHOD based test run that succeeds", "[class][template]", int, float, double) {
    CHECK( Template_Fixture<TestType>::m_a == 1.000000000000000000000000000000000000001 );
    CHECK( Template_Fixture<TestType>::m_a == 1.0000000000000000000000000000000001 );
}

template<typename T>
struct Template_Template_Fixture {
    Template_Template_Fixture() {}

    T m_a;
};

template<typename T>
struct Foo_class {
    size_t size() {
        return 0;
    }
};

TEMPLATE_PRODUCT_TEST_CASE_METHOD(Template_Template_Fixture, "A TEMPLATE_PRODUCT_TEST_CASE_METHOD based test succeeds", "[class][template]", (Foo_class, std::vector), int) {
    REQUIRE( Template_Template_Fixture<TestType>::m_a.size() == 0 );
}

/*--------------------------------------------------------*/

catch可以使用自带默认main()函数,也可以使用自己写的main()。
上述代码可以结合下面的代码,实现自己定义main()函数:

//
// Created by toson on 19-3-22.
//

//#define CATCH_CONFIG_MAIN //定义后直接使用catch自带的main()函数

#ifdef CATCH_CONFIG_MAIN
#include "../third/catch.hpp"
#else

#define CATCH_CONFIG_RUNNER //使用自己写的mian()函数
#include "../third/catch.hpp"

#include <chrono>
using namespace std;
using namespace std::chrono;

//If you just need to have code that executes before and/ or after Catch,
// this is the simplest option.
int main( int argc, char* argv[] ) {
    // global setup...
    steady_clock::time_point recordtime = steady_clock::now(); //用于记录执行时间

    int result = Catch::Session().run( argc, argv );

    // global clean-up...
    std::cout << "Catch"
              << CATCH_VERSION_MAJOR << "."
              << CATCH_VERSION_MINOR << "."
              << CATCH_VERSION_PATCH
              << " all test cost "
              << duration_cast<chrono::milliseconds>(steady_clock::now() - recordtime).count()
              << "ms" << endl;

    return result;
}

//If you still want Catch to process the command line,
// but you want to programmatically tweak the config:
//int main( int argc, char* argv[] )
//{
//    Catch::Session session; // There must be exactly one instance
//
//    // writing to session.configData() here sets defaults
//    // this is the preferred way to set them
//
//    int returnCode = session.applyCommandLine( argc, argv );
//    if( returnCode != 0 ) // Indicates a command line error
//        return returnCode;
//
//    // writing to session.configData() or session.Config() here
//    // overrides command line args
//    // only do this if you know you need to
//
//    int numFailed = session.run();
//
//    // numFailed is clamped to 255 as some unices only use the lower 8 bits.
//    // This clamping has already been applied, so just return it here
//    // You can also do any post run clean-up here
//    return numFailed;
//}
#endif

猜你喜欢

转载自blog.csdn.net/Tosonw/article/details/89449029