一、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