作者:CppExplore
http://www.shnenglu.com/CppExplore/和
http://blog.csdn.net/cppexplore同步發布
各個對象組織結構如下:

職責簡述如下:
對象
|
職責
|
TestAssert
|
測試斷言:判定測試結果是否正確,一般類似斷言表達。
|
TestCase
|
測試用例:多個測試斷言組成一個測試用例。測試對象為一個類中的一個具體方法。
|
TestSuite
|
測試套件:多個測試用例組成一個測試套件。測試對象為一個類。
|
MainTestSuite
|
主測試套件:單元測試運行主程序入口。測試用例也可繞過測試套件,直接包含在主測試套件中。
|
TestFixture
|
測試夾具:用于測試前的初始化操作以及測試后的清理操作,一般包括準備測試的前置條件/測試對象的狀態設置等。
|
當前存在很多流行的單元測試框架:衍生自JUnit的CppUnit,以及簡化版本的CppUnitLite,Boost.Test測試框架,Google Test測試框架等。每個測試框架都很完善,都可勝任單元測試任務。
從使用簡單性考慮,依次是Boost.Test>>Google Test>>CppUnitLite>>CppUnit.
本文選擇Boost.Test的單元測試框架講解。對Google Test感興趣的可參看http://www.cnblogs.com/coderzh/archive/2009/03/31/1426758.html。
講解TestAssert前,先說下Boost測試框架的日志級別,有以下9個級別:
級別名稱
|
說明
|
all / success
|
報告包括成功測試通知的所有日志信息
|
test_suite
|
顯示測試套件信息
|
message
|
顯示用戶信息
|
warning
|
報告用戶發出的警告
|
error
|
報告所有錯誤情況
|
cpp_exception
|
報告未捕獲的 C++ 異常
|
system_error
|
報告系統引起的非致命錯誤 (例如,超時或浮點數異常)
|
fatal
|
用戶或系統引起的致命錯誤 (例如,內存訪問越界)
|
nothing
|
不報告任何信息
|
生成測試可執行程序后,可以通過指定--log_level參數指定日志接別。比如,最后可執行程序為testmini,執行./testmini --log_level=warning指定在warning級別運行,默認執行在error級別。
請特別關注級別中黑體部分,TestAssert中將使用到。
Boost.Test中測試斷言包含如下3大類:
類別
|
功能
|
說明
|
WARN
|
打印warning日志,不增加失敗引用計數,繼續執行程序
|
檢驗不太重要但是正確的方面
|
CHECK
|
打印error日志,增加失敗引用計數,繼續執行程序
|
實現 assertions
|
REQUIRE
|
增加失敗應用計數,中斷程序的運行
|
失敗就不應該讓程序繼續運行則使用
|
Boost.Test中詳細測試斷言包含如下幾種:
TestAssert類型
|
說明
|
舉例
|
BOOST_WARN
|
WARN型預言檢測
|
BOOST_WARN(2+2==4);
|
BOOST_CHECK
|
CHECK型預言檢測
|
BOOST_CHECK(2+2==4);
|
BOOST_REQUIRE
|
REQUIRE型預言檢測
|
BOOST_REQUIRE(2+2==4);
|
BOOST_WARN_MESSAGE
|
WARN型預言檢測,自定義日志
|
BOOST_WARN_MESSAGE(2+2==4,"description…" );
|
BOOST_CHECK_MESSAGE
|
CHECK型預言檢測,自定義日志
|
BOOST_CHECK_MESSAGE(2+2==4,"description…" );
|
BOOST_REQUIRE_MESSAGE
|
REQUIRE型預言檢測,自定義日志
|
BOOST_ REQUIRE _MESSAGE(2+2==4,"description…" );
|
BOOST_ERROR
|
同BOOST_CHECK_MESSAGE( false, M )
|
if( 2+2 !=4 )
BOOST_ERROR( "description…" );
|
BOOST_FAIL
|
同BOOST_REQUIRE_MESSAGE( false, M )
|
if( 2+2 !=4 )
BOOST_FAIL( "description…" );
|
BOOST_WARN_EQUAL
|
WARN型左右值相等檢測檢測
|
BOOST_WARN_EQUAL(2+2,4);
|
BOOST_CHECK_EQUAL
|
CHECK型左右值相等檢測檢測
|
BOOST_CHECK_EQUAL(2+2,4);
|
BOOST_REQUIRE_EQUAL
|
REQUIRE型左右值相等檢測檢測
|
BOOST_REQUIRE_EQUAL(2+2,4);
|
該類還有不等,小于,大于,小于等于,大于等于的判別檢測,此處只羅列WARN的,其他不再一一羅列:BOOST_WARN_NE/ BOOST_WARN_LT/ BOOST_WARN_LE/ BOOST_WARN_GT/ BOOST_WARN_GE
|
BOOST_WARN_THROW
|
WARN型,判別執行函數期,有異常拋出。
|
BOOST_WARN_THROW(executeSql(“select…”),oracle::ErrorCodeException);
|
BOOST_CHECK_THROW
|
CHECK型,判別執行函數期,有異常拋出。
|
|
BOOST_REQUIRE_THROW
|
REQUIRE型,判別執行函數期,有異常拋出。
|
|
BOOST_WARN_EXCEPTION
|
WARN型,執行函數,當預言為真時,捕獲異常。
|
BOOST_WARN_THROW(executeSql(“select…”),oracle::ErrorCodeException,2+2==4);
|
BOOST_CHECK_EXCEPTION
|
CHECK型,執行函數,當預言為真時,捕獲異常。
|
|
BOOST_REQUIRE_EXCEPTION
|
REQUIRE型,執行函數,當預言為真時,捕獲異常。
|
|
BOOST_WARN_NO_THROW
|
WARN型,判別執行函數期,無異常拋出。
|
BOOST_WARN_NO_THROW(executeSql(“select…”));
|
BOOST_CHECK_NO_THROW
|
CHECK型,判別執行函數期,無異常拋出。
|
|
BOOST_REQUIRE_NO_THROW
|
REQUIRE型,判別執行函數期,無異常拋出。
|
|
BOOST_WARN_CLOSE
|
WARN型,判定左右值是否足夠逼近。用于浮點數比較。
|
BOOST_WARN_CLOSE(2.1131,2.1132,0.01)
|
BOOST_CHECK_CLOSE
|
CHECK型,判定左右值是否足夠逼近。用于浮點數比較
|
|
BOOST_REQUIRE_CLOSE
|
REQUIRE型,判定左右值是否足夠逼近。用于浮點數比較
|
|
BOOST_WARN_SMALL
|
WARN型,判定值是否足夠小(是否接近0)。用于浮點數比較
|
BOOST_WARN_CLOSE(0.1,0.01)
|
BOOST_CHECK_SMALL
|
CHECK型,判定值是否足夠小(是否接近0)。用于浮點數比較
|
|
BOOST_REQUIRE_SMALL
|
REQUIRE型,判定值是否足夠小(是否接近0)。用于浮點數比較
|
|
對于TestCase/TestSuite等,Boost.Test既支持手動注冊方式,也支持自動注冊方式,當前Boost官方推薦自動注冊方式,手動注冊為了保持向前兼容保留,以后版本可能被移除。使用宏BOOST_AUTO_TEST_CASE即可自動注冊測試用例。使用如下:
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_CASE(test_case_name)
{
BOOST_CHECK(true);
}
|
使用宏BOOST_AUTO_TEST_SUITE(test_suite_name)開始測試套件,使用BOOST_AUTO_TEST_SUITE_END()結束測試套件。使用舉例:
#include <boost/test/unit_test.hpp>
BOOST_AUTO_TEST_SUITE(test_suite_name)
BOOST_AUTO_TEST_CASE(test_case1)
{
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_CASE(test_case1)
{
BOOST_CHECK(true);
}
BOOST_AUTO_TEST_SUITE_END()
|
使用宏BOOST_TEST_MODULE表明主測試套件,一個測試項目中只能存在一個主測試套件。使用舉例:
#define BOOST_TEST_MODULE maintest
#include <boost/test/unit_test.hpp>
|
測試夾具做測試前的準備工作和測試后的清理工作。而C++的RAII機制(構造函數申請資源,析構函數釋放資源)恰好能滿足該需求。因此Boost中直接使用普通類做夾具。實現夾具舉例:該夾具在測試前將整數i初始化為5
struct MyFixture
{
MyFixture():i(5){}
~MyFixture(){}
Int I;
};
|
夾具可以和TestCase一起使用,也可以和TestSuite一起使用,也可以和MainTestSuite一起使用。
使用宏BOOST_FIXTURE_TEST_CASE(test_case_name, fixure_name)代替BOOST_AUTO_TEST_CASE(test_case_name)即可在TestCase中使用夾具,舉例如下:
BOOST_FIXTURE_TEST_CASE(test_case_name,MyFixture)
{
BOOST_CHECK(i==5);
}
|
使用宏BOOST_FIXTURE_TEST_SUITE(suite_name, fixure_name)代替BOOST_AUTO_TEST_SUITE(suite_name)即可在TestSuite中使用夾具,舉例如下:
BOOST_FIXTURE_TEST_SUITE(test_suite, MyFixture );
BOOST_AUTO_TEST_CASE (test_case1)
{
BOOST_CHECK(i==5);
}
BOOST_AUTO_TEST_CASE(test_case2)
{
BOOST_CHECK(++i==6);
}
BOOST_AUTO_TEST_SUITE_END()
|
使用宏BOOST_GLOBAL_FIXTURE(MyFixture);將MyFixture聲明為全局夾具,即可和MainTestSuite一起使用。
對已經完成的項目做單元測試,假定該項目具有很好的測試性:
1) 對項目中的每個類對象創建一個測試套件,一個測試套件對應一個cpp文件。對類的每個類方法創建一個測試用例,這些測試用例均包含上前面的測試套件中。每個測試用例可以有多個測試斷言,對該方法進行充分測試。
2) 在測試主文件中定義宏BOOST_TEST_MODULE,并包含所有的測試套件文件。
3) Linux下,將被測試項目編譯成靜態庫(將main函數外的所有文件編譯打包)供測試項目連接。Window下為測試項目做靜態庫工程,設置測試工程依賴該工程。并將頭文件路徑設置正確,即可編譯運行。
附件為一示例項目以及對應單元測試工程舉例,項目目錄下make編譯生成靜態庫以及可執行程序,test目錄下make生成單元測試可執行程序。(略)
1、 內部依賴問題
類之間相互協作共同完成功能,類之間的依賴必不可少。為了測試某個類,必須實例化它依賴的類,而它依賴的類又可能依賴其他類,因此必須實例化其他類。如此一環扣一環,可能把整個項目大部分類都包含在了這次測試中,最后做的不是單元測試,而是掛著單元測試外殼的集成測試。
2、 外部依賴問題
很多項目,尤其是我們的網絡應用服務器,運行期間需要依賴外部的其他服務器或者數據庫或者本地的文件系統。而對很多外部的依賴很難模擬,或者說模擬成本太高,往往讓測試者望而卻步。
3、 函數本身問題
項目中的很多或者可能是大部分函數,是沒有明確返回值或者無異常拋出,而只是和其他外設交互。難于使用測試斷言判定。
以上造成很難將某個類從項目中隔離出來,難以設置單元測試點。
上述困難均為依賴造成。
1、內部解依賴
對被測代碼進行解依賴,強化設計,減少耦合,提高代碼可測性。解依賴的過程也即為對代碼重構過程,減少類間耦合,制造接口層。常用手段有:虛函數、函數指針、傳遞參數等方式。而對于難于進行解依賴情況,就要考慮提取分化重寫方法。
2、寫樁代碼模擬外部環境
單元測試不能直接依賴外部環境,必須寫樁代碼模擬。而外部環境的可模擬性與內部解依賴緊密相關。對于外部的隨機性和各種不確定性,樁代碼必須盡可能模擬。
3、開辟訪問類私有屬性通道
有些類方法雖然沒有明確返回值,但可能修改類的內部狀態,可以通過判斷類的私有屬性來判定類方法的執行情況。可以給類增加Get()方法或者將私有屬性設置為protected。
更重要的是在代碼開發期,引入TDD思維,強化設計,提高代碼可測性,提高代碼的整體質量