一、前言
上一篇我們分析了gtest的一些內部實現,總的來說整體的流程並不複雜。本篇我們就嘗試編寫一個精簡版本的C++單元測試框架:nancytest ,通過編寫這個簡單的測試框架,將有助於我們理解gtest。
二、整體設計
使用最精簡的設計,我們就用兩個類,夠簡單吧:
1. TestCase類
包含單個測試案例的信息。
2. UnitTest類
負責所有測試案例的執行,管理。
三、TestCase類
TestCase類包含一個測試案例的基本信息,包括:測試案例名稱,測試案例執行結果,同時還提供了測試案例執行的方法。我們編寫的測試案例都繼承自TestCase類。
{
public:
TestCase(const char* case_name) : testcase_name(case_name){}
// 執行測試案例的方法
virtual void Run() = 0;
int nTestResult; // 測試案例的執行結果
const char* testcase_name; // 測試案例名稱
};
四、UnitTest類
我們的UnitTest類和gtest的一樣,是一個單件。我們的UnitTest類的邏輯非常簡單:
1. 整個進程空間保存一個UnitTest 的單例。
2. 通過RegisterTestCase()將測試案例添加到測試案例集合testcases_中。
3. 執行測試案例時,調用UnitTest::Run(),遍歷測試案例集合testcases_,調用案例的Run()方法
{
public:
// 獲取單例
static UnitTest* GetInstance();
// 註冊測試案例
TestCase* RegisterTestCase(TestCase* testcase);
// 執行單元測試
int Run();
TestCase* CurrentTestCase; // 記錄當前執行的測試案例
int nTestResult; // 總的執行結果
int nPassed; // 通過案例數
int nFailed; // 失敗案例數
protected:
std::vector<TestCase*> testcases_; // 案例集合
};
下面是UnitTest類的實現:
{
static UnitTest instance;
return &instance;
}
TestCase* UnitTest::RegisterTestCase(TestCase* testcase)
{
testcases_.push_back(testcase);
return testcase;
}
int UnitTest::Run()
{
nTestResult = 1;
for (std::vector<TestCase*>::iterator it = testcases_.begin();
it != testcases_.end(); ++it)
{
TestCase* testcase = *it;
CurrentTestCase = testcase;
std::cout << green << "======================================" << std::endl;
std::cout << green << "Run TestCase:" << testcase->testcase_name << std::endl;
testcase->Run();
std::cout << green << "End TestCase:" << testcase->testcase_name << std::endl;
if (testcase->nTestResult)
{
nPassed++;
}
else
{
nFailed++;
nTestResult = 0;
}
}
std::cout << green << "======================================" << std::endl;
std::cout << green << "Total TestCase : " << nPassed + nFailed << std::endl;
std::cout << green << "Passed : " << nPassed << std::endl;
std::cout << red << "Failed : " << nFailed << std::endl;
return nTestResult;
}
五、NTEST宏
接下來定一個宏NTEST,方便我們寫我們的測試案例的類。
testcase_name##_TEST
#define NANCY_TEST_(testcase_name) \
class TESTCASE_NAME(testcase_name) : public TestCase \
{ \
public: \
TESTCASE_NAME(testcase_name)(const char* case_name) : TestCase(case_name){}; \
virtual void Run(); \
private: \
static TestCase* const testcase_; \
}; \
\
TestCase* const TESTCASE_NAME(testcase_name) \
::testcase_ = UnitTest::GetInstance()->RegisterTestCase( \
new TESTCASE_NAME(testcase_name)(#testcase_name)); \
void TESTCASE_NAME(testcase_name)::Run()
#define NTEST(testcase_name) \
NANCY_TEST_(testcase_name)
六、RUN_ALL_TEST宏
然後是執行所有測試案例的一個宏:
UnitTest::GetInstance()->Run();
七、斷言的宏EXPECT_EQ
這裏,我只寫一個簡單的EXPECT_EQ :
if (m != n) \
{ \
UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; \
std::cout << red << "Failed" << std::endl; \
std::cout << red << "Expect:" << m << std::endl; \
std::cout << red << "Actual:" << n << std::endl; \
}
八、案例Demo
夠簡單吧,再來看看案例怎麼寫:
int Foo(int a, int b)
{
return a + b;
}
NTEST(FooTest_PassDemo)
{
EXPECT_EQ(3, Foo(1, 2));
EXPECT_EQ(2, Foo(1, 1));
}
NTEST(FooTest_FailDemo)
{
EXPECT_EQ(4, Foo(1, 2));
EXPECT_EQ(2, Foo(1, 2));
}
int _tmain(int argc, _TCHAR* argv[])
{
return RUN_ALL_TESTS();
}
整個一山寨版gtest,呵。執行一下,看看結果怎麼樣:
九、總結
本篇介紹性的文字比較少,主要是我們在上一篇深入解析gtest時已經將整個流程弄清楚了,而現在編寫的nancytest又是其非常的精簡版本,所有直接看代碼就可以完全理解。希望通過這個Demo,能夠讓大家對gtest有更加直觀的瞭解。回到開篇時所說的,我們沒有必要每個人都造一個輪子,因爲gtest已經非常出色的爲我們做好了這一切。如果我們每個人都寫一個自己的框架的話,一方面我們要付出大量的維護成本,一方面,這個框架也許只能對你有用,無法讓大家從中受益。
gtest正是這麼一個優秀C++單元測試框架,它完全開源,允許我們一起爲其貢獻力量,並能讓更多人從中受益。如果你在使用gtest過程中發現gtest不能滿足你的需求時(或發現BUG),gtest的開發人員非常急切的想知道他們哪來沒做好,或者是gtest其實有這個功能,但是很多用戶都不知道。所以你可以直接聯繫gtest的開發人員,或者你直接在這裏回帖,我會將您的意見轉告給gtest的主要開發人員。
如果你是gtest的超級粉絲,原意爲gtest貢獻代碼的話,加入他們吧。
本Demo代碼下載:/Files/coderzh/Code/nancytest.rar
本篇是該系列最後一篇,其實gtest還有更多東西值得我們去探索,本系列也不可能將gtest介紹完全,還是那句話,想了解更多gtest相關的內容的話:
訪問官方主頁:http://code.google.com/p/googletest/
下載gtest源碼: http://code.google.com/p/googletest/downloads/list