單元測試架構
在開發實踐中,我目前常用的單元測試架構主要是兩個:GTest與TUT。關於GTest我會在其他文章中詳細介紹,本文將主要介紹TUT架構的特點及簡單的使用方法。
TUT(Template Unit Tests)
TUT的官方首頁:http://tut-framework.sourceforge.net/index.html
TUT的SVN地址:http://tut-framework.svn.sourceforge.net/svnroot/tut-framework/trunk
TUT的使用者手冊:http://tut-framework.sourceforge.net/doxygen/index.html
TUT是一個使用C++編寫的單元測試架構,正如名稱所示,它是一套C++模板庫。下載代碼後我們可以發現:它的全部核心是由一系列的.hpp檔案組成,不包含任何源檔案(example除外),因此,我們在進行單元測試時只需要包含tut的檔案即可,無需對TUT進行編譯或者連結,像使用STL一樣輕鬆自由,這也是我喜歡它的原因。
TUT使用方法
既然是一個輕量級測試架構,我們應該在半個小時內上手,行了,廢話少說,假定我們開發了下面的一個類庫:
COperator.h
#include <stdio.h>class COperator{private: int m_value;public: COperator(int value) { m_value = value; } int add(int value) { return m_value += value; } int sub(int value) { return m_value -= value; }};
我們希望對COperator的每個介面進行單元測試,則可以針對COperator類定義一個TestGroup,然後在這個TestGroup編寫多個TestCase,每個TestCase對一個或幾個介面進行測試,測試代碼如下:
test_COperator.cpp
#include <stdio.h> #include <tut/tut.hpp> namespace tut{ // 每個測試組都必須基於一個測試資料的類型才能建立,可以在這個資料結構中放一些可能被該組中多個測試案例使用的資料或者方法, // 如果沒有,也可以什麼都不填。最好不要通過此結構的成員在不同測驗用例間傳遞資料。 struct COperatorTestData { int GetTestNum() { static const int TestNums[] = { 1, 3, 5, 7, 9 }; static int i = 0; static const int num = sizeof(TestNums) / sizeof(int); return TestNums[ i++ % num]; } }; // 建立一個test_group對象時,在建構函式中用一個字串指明該test group的名稱; // 當存在多個test group時,每個group的名稱應該不同,這樣可以在啟動時單獨指定某個test group進行測試; // 預設情況下,一個test_group最多50個test case,如果希望更多case,可以在定義時指明上限,如: // test_group testgroup("COperator",1000); static test_group testgroup("COperator"); template<> template<> void test_group::object::test<1>() // 編寫第一個測試案例 { set_test_name("Test COperatorTestData::add"); int src = GetTestNum(); COperator op(src) ; ensure("Test COperatorTestData::Add Failed", op.add(1) == src + 1); } template<> template<> void test_group::object::test<2>() // 編寫第二個測試案例 { set_test_name("Test COperatorTestData::sub"); int src = GetTestNum(); COperator op(src) ; ensure("Test COperatorTestData::Sub Failed", op.sub(2) == src - 2); } template<> template<> void test_group::object::test<3>() // 沒事找茬,寫個錯誤case看看 { set_test_name("Test Tut::ensure"); int src = GetTestNum(); COperator op(src) ; ensure("Test COperatorTestData::Sub Failed", op.sub(2) == src - 1); }}
如果還需要測試其他類,我也可以在編寫如test_*.cpp等一系列單元測試用例,最終通過main函數將用例綜合起來: test_main.cpp
#include <tut/tut.hpp> #include <tut/tut_console_reporter.hpp> #include <tut/tut_main.hpp> #include <iostream> static tut::test_runner_singleton runner;int main(int argc, const char* argv[]){ tut::console_reporter reporter; tut::runner.get().set_callback(&reporter); try { if(tut::tut_main(argc, argv)) { if(reporter.all_ok()) { return 0; } else { std::cerr << "/nFAILURE and EXCEPTION in these tests are FAKE ;)" << std::endl; } } } catch(const tut::no_such_group &ex) { std::cerr << "No such group: " << ex.what() << std::endl; } catch(const tut::no_such_test &ex) { std::cerr << "No such test: " << ex.what() << std::endl; } catch(const tut::tut_error &ex) { std::cout << "General error: " << ex.what() << std::endl; } return 0;}
我們只需要將test_*.cpp連起來編譯即可,假如產生一個test.exe檔案,我們可以用
test.exe --help 查看協助
test.exe 運行所有TestGroup
test.exe "COperator" 運行名稱為"COperator"的TestGroup
test.exe "COperator" 1 運行名稱為"COperator"的TestGroup的第一個測試案例。
TUT的輸出
TUT目前支援三種報告的輸出方式:console_reporter, xml_reporter,cppunit_reporter。另外,也可以自己繼承tut::callback,編寫自己的輸出方式。下面,以xml方式輸出為例,可以如下修改test_main.cpp:
...//#include <tut/tut_console_reporter.hpp> #include <tut/tut_xml_reporter.hpp> ...int main(int argc, const char* argv[]){ tut::xml_reporter reporter(std::cout); // 將xml輸出到stdio,也可以選擇輸出到某個字串中 tut::runner.get().set_callback(&reporter); ...}
執行所有用例,輸出結果如下:
<xml version="1.0" encoding="utf-8" standalone="yes"?><testsuites> <testsuite errors="0" failures="1" tests="3" name="COperator"> <testcase classname="COperator" name="Test COperatorTestData::add"/> <testcase classname="COperator" name="Test COperatorTestData::sub"/> <testcase classname="COperator" name="Test Tut::ensure"> <failure message="Test COperatorTestData::Sub Failed" type="Assertion">Test COperatorTestData::Sub Failed</failure> <testcase> <testsuite><testsuites>
TUT的斷言
TUT支援一系列ensure開頭的斷言函數,一旦宣告失敗則該case不再繼續執行,轉而執行下一個case,下面介紹幾種常用的斷言,其餘的建議自己參考tut_assert.hpp與tut_posix.hpp
// 斷言某個條件為真void ensure(bool cond);// 斷言某個條件為假void ensure_not(bool cond);// 斷言某個條件為真,否則輸出msgtemplate <typename M>void ensure(const M& msg, bool cond);// 與ensure類似,不過在posix系統中會在msg後輸出errnotemplate<typename M>void ensure_errno(const M& msg, bool cond);// 斷言某個條件假,否則輸出msg template <typename M>void ensure_not(const M& msg, bool cond);// 斷言兩個值相等template <typename LHS, typename RHS>void ensure_equals(const LHS& actual, const RHS& expected);// 斷言兩個值相等,否則輸出msg template <typename M, typename LHS, typename RHS>void ensure_equals(const M& msg, const LHS& actual, const RHS& expected);// 斷言兩個浮點數相差不超過某個限定值,否則輸出msgtemplate<typename M>void ensure_equals(const M& msg, const double& actual, const double& expected, const double& epsilon);// 斷言兩個集合相等,否則輸出msgtemplate<typename LhsIterator, typename RhsIterator>void ensure_equals(const std::string &msg, const LhsIterator &lhs_begin, const LhsIterator &lhs_end, const RhsIterator &rhs_begin, const RhsIterator &rhs_end);// 斷言兩個值距離小於distance,否則輸出msgtemplate <typename M, class T>void ensure_distance(const M& msg, const T& actual, const T& expected, const T& distance);// 相當於直接斷言錯誤,並輸出msgvoid fail(const char* msg = "");// 跳過某個測試案例剩餘的代碼(不視為斷言錯誤),並列印msgvoid skip(const char* msg = "");
TUT的斷言
作者:icefireelf
出處:http://blog.csdn.net/icefireelf/article/details/6363126