導讀
C++ 開發時我們常有一個非常期望的願景,那就是引用第三方庫和框架時希望儘可能的簡單,不然各種平臺、各種編譯問題可以讓人焦頭爛額。而Catch2
就是一個只有頭文件的單元測試框架。放心,這個單元測試框架完全能夠支撐你的項目,且它的協議是 Boost Software License,完全可以商用。
由於Catch2
只有一個頭文件,因此你只需要下載這個頭文件下來,添加到你的項目中就可以了。
github 下載地址:catchorg/Catch2
不能翻牆的提供csdn下載:
catchorg/Catch2
使用案例
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
int Factorial( int number ) {
return number <= 1 ? number : Factorial( number - 1 ) * number; // fail
// return number <= 1 ? 1 : Factorial( number - 1 ) * number; // pass
}
TEST_CASE( "Factorial of 0 is 1 (fail)", "[single-file]" ) {
REQUIRE( Factorial(0) == 1 );
}
TEST_CASE( "Factorials of 1 and higher are computed (pass)", "[single-file]" ) {
REQUIRE( Factorial(1) == 1 );
REQUIRE( Factorial(2) == 2 );
REQUIRE( Factorial(3) == 6 );
REQUIRE( Factorial(10) == 3628800 );
}
這個例子是官方案例,演示了計算階乘的算法。
常規測試
#define CATCH_CONFIG_MAIN
這個宏定義了catch2 的 main 函數。下面的代碼是從 catch2 裏面摘抄的,可以看到 main 函數定義。
#ifdef CATCH_CONFIG_MAIN
// start catch_default_main.hpp
#ifndef __OBJC__
#if defined(CATCH_CONFIG_WCHAR) && defined(WIN32) && defined(_UNICODE) && !defined(DO_NOT_USE_WMAIN)
// Standard C/C++ Win32 Unicode wmain entry point
extern "C" int wmain (int argc, wchar_t * argv[], wchar_t * []) {
#else
// Standard C/C++ main entry point
int main (int argc, char * argv[]) {
#endif
return Catch::Session().run( argc, argv );
}
意味着你不需要自己寫 main 函數。
但是如果你需要寫自己的 main 函數,catch2 也支持,像下面這樣:
#define CATCH_CONFIG_RUNNER
#include "catch.hpp"
int main( int argc, char* argv[] ) {
// global setup...
int result = Catch::Session().run( argc, argv );
// global clean-up...
return result;
}
大多數情況下我們都是有 main 函數的,當你運行項目時Catch::Session().run( argc, argv );
這一句就啓動了你的單元測試。如下:
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
int main(int argc, char* argv[]) {
// global setup...
int result = Catch::Session().run(argc, argv);
// global clean-up...
return result;
}
int Factorial(int number) {
return number <= 1 ? number : Factorial(number - 1) * number; // fail
}
TEST_CASE("Factorial of 0 is 1 (fail)", "[single-file]") {
REQUIRE(Factorial(0) == 1);
}
TEST_CASE("Factorials of 1 and higher are computed (pass)", "[single-file]") {
REQUIRE(Factorial(1) == 1);
REQUIRE(Factorial(2) == 2);
REQUIRE(Factorial(3) == 6);
REQUIRE(Factorial(10) == 3628800);
}
回到最上面的測試案例,我們的 test-case 是有命名的,其實 test-case 也是可以沒有名字的,因爲要測試的函數多了,你最終總是要命名。不命名的test-case 如下:
TEST_CASE() {
REQUIRE(Factorial(1) == 1);
}
簡單吧。 剩下的就是去補充你的單元測試文件了,直到你寫完了自己需要的 test-case。
看下單元測試運行結果:
16 行測試沒有通過,和代碼相符。
BDD 風格測試
BDD 簡介:
Behavior Driven Development,行爲驅動開發是一種敏捷軟件開發的技術,它鼓勵軟件項目中的開發者、QA和非技術人員或商業參與者之間的協作,BDD 提倡的是通過將測試語句轉換爲類似自然語言的描述,開發人員可以使用更符合大衆語言的習慣來書寫測試,當別人接手/交付,或者自己修改的時候,都簡單易明白,順利很多。一個典型的 BDD 測試用例包括完整的三段式上下文,測試大多可以翻譯爲Given…When…Then的格式,讀起來輕鬆愜意。
catch2 也支持像 BDD(行爲驅動開發)風格的單元測試。
因爲我們目前的項目是採用敏捷開發的模式,同時基於 BDD 開發,需求人員在寫需求是是按照 GIVEN WHEN THEN 的方式,截張圖看下(真實圖片,理解打碼):
所以我們的單元測試需要跟隨 BDD 流程來。這樣可以保證所有參與者的理解是一致的,包括代碼也是跟隨需求描述是一致的。
案例:
#define CATCH_CONFIG_MAIN
#include "catch.hpp"
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() == 4 );
REQUIRE( v.capacity() >= 5 );
}
}
}
}
真實項目不便演示,上面的代碼也是官方給的 BDD 風格的單元測試。我在 42 行故意寫錯,看下運行結果:
結果提示 42 行測試不通過。
結語
基於上面的小案例,我相信你已經迅速掌握了 catch2 的用法,也可以給自己的代碼寫單元測試了。只有當你真正的實踐過,你才能知道這裏面的門道。
如有幫助,請多多點贊支持。