以前編寫程序從沒有做過單元測試的工作,所以在后期會花很多時間去糾錯,這也就是軟件工程中的2:8定律。最近要完成一個項目,要求要對系統中的主類和主函數作出單元測試的保證,才去查找了相關方面的資料,看過后覺得單元測試在工程中是必不可少的一項,下面就對有關C++的單元測試做一個簡單的介紹,因為本人還沒有系統的去理解相關方面的內容,所以只是介紹一些簡單的應用和淺顯的原理,有不到之處還請指出:
1) 什么是單元測試
單元測試(unit test,模塊測試又稱白盒測試)是開發者編寫的一小段代碼,用於檢測被測代碼的一個很小的,很明確的功能是否正確,通過編寫單元測試可以在編碼階段發現程序編碼錯誤,甚至是程序設計錯誤。現在回想下我們平時編程的過程:梳理邏輯->編寫代碼->調bug的一個循環過程;其中調試bug是一個很耗費時間的過程,而且調完一堆bug后我們對自己程序的信任度卻在不斷降低,因為不知道還會不會出bug。這是因為我們所調試的bug只是當前出現的,沒有出現的呢,我們就說不好了。我們知道程序中一個簡單的if...else...語句都會對數據流有一次分類,不同類的數據流所對應的操作不同,想想這些你就會覺得自己的代碼還不知道要出多少bug。單元測試卻很好的解決了這個問題,你要做的就是對核心的函數,核心的邏輯,將所有可能的數據流都列出來,進行一次全覆蓋測試,這樣你代碼的健壯性就會很強。
2)如何寫單元測試
最簡單的方法那就是自己將核心的函數抽出來,重新建立一個項目,提供所有可能的數據流入口,對函數進行測試。這樣做有兩個缺陷:1、只能處理一些簡單的項目,對於邏輯較為復雜,數據分支較多的就無法操作了;2、沒有一個很好的框架,寫出的代碼沒有一個統一的規則,可讀性不強;為此,依托一個現有的測試框架是必不可少的。CppUnit是xunit家族的一員,xunit是一種測試框架,最早是在smalltalk上實現,后來被廣泛的在各種語言上實現,除了Cppunit還有NUnit(c#版本)phpunit(php版本)和CppUnit相似的gtest(google的C++測試工具,據說在應用上要優於CppUnit,沒有用過,大家可以試試),cmockery(c語言測試工具,也是google的)。cppunit以源碼的方式發布,所以想要使用這個工具就要自己下載編譯(后面會介紹到),同時在windows下CppUnit還帶了一個MFC項目,可以以圖形界面的方式報告編譯情況。
3)如何編譯CppUnit
我的編譯環境是win7,vs2010;
1 :下載源碼包 最新的版本是1.21.1(2008年最后一次更新)
地址 :http://sourceforge.net/projects/cppunit/ 或者直接點擊 http://sourceforge.net/projects/cppunit/files/cppunit/1.12.1/cppunit-1.12.1.tar.gz/download
2 : 復制cppunt-1.12.1到c:\解壓得到目錄C:\cppunit-1.12.1
3 : 進入C:\cppunit-1.12.1\src 用VS2010打開CppUnitLibraries.dsw提示轉換,轉換之
4 : 在項目列表看見一堆項目,默認是以Debug的方式編譯,如以該方式編譯需要修改目標文件名
cppunit $(ProjectName) -> $(ProjectName)d
cppunit_dll $(ProjectName) -> cppunitd_dll
DllPlugInTester $(ProjectName) -> $(ProjectName)d_dll
TestPlugInRunner $(ProjectName) -> $(ProjectName)d
TestRunner $(ProjectName) -> $(ProjectName)d
需要修改目標文件名的原因是上述項目都設置了生成事件,在生成以后都會把生成的文件復制到lib目錄下,個人判斷d應該是代表了測試版的意思,如果是Release版本生成的目標文件都是項目名不需要修改
5 : 修改DSPlugIn入口
屬性,配置屬性,鏈接器,高級,無入口點 設置是
6 : 修改無法加載類型庫(編譯TestPlugInRunner,TestRunner)會報錯其中7.0修改為8.0
#import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("7.0") lcid("0") raw_interfaces_only named_guids
以上設置都全做完以后就可以整體編譯解決方案了(這里要注意一點,要將上述所有的配置實現后再編譯,不要對每個項目進行單獨編譯;否則會出現諸如“該文件沒有一個關聯的程序...”這樣的問題)
4)如何用Cppunit
先寫一個測試的Demo,初步體驗下Cppunit的用法
1、打開vs2010文件項, 新建VC++空項目:cppUnitTest
2、 設置環境變量;在系統變量PATH的尾巴上添加“C:\cppunit-1.12.1”(這是我的地址)
3、 [項目cppUnitTest]-[屬性]-[配置屬性]-[C/C++]-[常規]-[附加包含目錄]: "../include"
4、 [項目cppUnitTest]-[屬性]-[配置屬性]-[鏈接器]-[常規]-[附加庫目錄]: "../lib"
5、 [項目cppUnitTest]-[屬性]-[配置屬性]-[鏈接器]-[輸入]-[附加依賴項]: "cppunitd.lib"
6、新建文件main.cpp
#include <cppunit/TestCase.h> #include <cppunit/TestResult.h> #include <cppunit/TestResultCollector.h> #include <cppunit/TextOutputter.h> #include <cppunit/TestCaller.h> #include <cppunit/TestRunner.h> // 定義測試類 class StringTest : public CppUnit::TestFixture { public: void setUp() // 初始化 { m_str1 = "Hello, world"; m_str2 = "Hi, cppunit"; } void tearDown() // 清理 { } void testSwap() // 測試方法1 { std::string str1 = m_str1; std::string str2 = m_str2; m_str1.swap(m_str2); CPPUNIT_ASSERT(m_str1 == str2); CPPUNIT_ASSERT(m_str2 == str1); } void testFind() // 測試方法2 { int pos1 = m_str1.find(','); int pos2 = m_str2.rfind(','); CPPUNIT_ASSERT_EQUAL(5, pos1); CPPUNIT_ASSERT_EQUAL(2, pos2); } protected: std::string m_str1; std::string m_str2; }; int main(int argc, char* argv[]) { CppUnit::TestResult r; CppUnit::TestResultCollector rc; r.addListener(&rc); // 准備好結果收集器 CppUnit::TestRunner runner; // 定義執行實體 runner.addTest(new CppUnit::TestCaller<StringTest>("testSwap", &StringTest::testSwap)); // 構建測試用例1 runner.addTest(new CppUnit::TestCaller<StringTest>("testFind", &StringTest::testFind)); // 構建測試用例2 runner.run(r); // 運行測試 CppUnit::TextOutputter o(&rc, std::cout); o.write(); // 將結果輸出 return rc.wasSuccessful() ? 0 : -1; }
以上代碼時使用Cppunit框架對string類的兩個函數進行測試,在testFind()中調用string的find函數並通過CPPUNIT_ASSERT對結果進行測試;
