既然fixture內(nèi)部可以定義setup,我們自然希望也能對(duì)稱地定義teardown,用于處理同一個(gè)fixture中所有測(cè)試用例共同的善后工作。
比如我們希望有如下的測(cè)試用例描述方式:
FIXTURE(TestTearDown)
{
TEARDOWN()
{
ASSERT_EQ(__int(0), expected);
}
TEST("test 1")
{
using expected = __int(0);
};
TEST("test 2")
{
using expected = __sub(__int(1), __int(1));
};
};
如上TEARDOWN
的定義一般在fixture中所有測(cè)試用例的前面,但執(zhí)行卻在每個(gè)測(cè)試用例的后面。要如何實(shí)現(xiàn)TEARDOWN
呢?借助前面實(shí)現(xiàn)setup的經(jīng)驗(yàn),似乎我們需要用戶通過TEARDOWN
定義一個(gè)類,每個(gè)測(cè)試用例能夠繼承它,然后再將自身傳遞給它。
于是我們想到CRTP(Curiously Recurring Template Pattern)
模式,它的用法如下:
template<TestCase>
struct TlpTestTeardown
{
// 可以在此處使用TestCase類型的內(nèi)部定義
};
struct TlpTestCase : TlpTestTearDown<TlpTestCase>
{
// ...
};
可以看到CRTP允許我們先定義一個(gè)父類模板,然后子類繼承這個(gè)模板的時(shí)候把自身當(dāng)做模板參數(shù)再傳遞給父類模板,于是父類模板中就可以使用子類類型了。
問題是對(duì)于完成teardown功能的父類模板到底如何使用作為每個(gè)測(cè)試用例的子類類型?最直接的想法是讓TlpTestTeardown再繼承自Test,這樣每個(gè)測(cè)試用例中的內(nèi)容就對(duì)TlpTestTeardown直接可見了。
template<TestCase>
struct TlpTestTeardown : TestCase
{
// ...
};
struct TlpTestCase : TlpTestTearDown<TlpTestCase>
{
// ...
};
遺憾的是,上述代碼并不能編譯通過。原因是TlpTestTeardown<TlpTestCase>
將TlpTestCase作為父類,所以編譯器編譯到這句的時(shí)候需要TlpTestCase的完整定義,而此時(shí)TlpTestCase才開始定義。
這警告了我們,如果TlpTestTearDown繼承了TlpTestCase,就得在TlpTestTearDown<TlpTestCase>
時(shí)刻看到TlpTestCase的完整定義。那么我們是否可以將TlpTestTearDown<TlpTestCase>
挪到TlpTestCase定義的后面。
struct TlpTestCase
{
// ...
};
struct : TlpTestTearDown<TlpTestCase>{};
這可以通過編譯,但是從此以后所有的測(cè)試用例后面都得加上struct : TlpTestTearDown<TlpTestCase>{}
。這意味著我們需要給宏TEST
實(shí)現(xiàn)一個(gè)對(duì)應(yīng)的結(jié)束宏,專門用來隱藏struct : TlpTestTearDown<TlpTestCase>{}
。這導(dǎo)致我們以后定義測(cè)試用例不能再使用常用的花括號(hào),而是要像下面這樣:
TEST_BEGIN("test description")
// 測(cè)試用例內(nèi)容實(shí)現(xiàn)于此
TEST_END() // 用于隱藏“}; struct : TlpTestTearDown<TlpTestCase>{};”
這很討厭!由于定義測(cè)試用例是如此的頻繁,我們還是希望保持之前那種簡(jiǎn)潔的定義方式!
我們?cè)倩氐?code>struct TlpTestCase : TlpTestTearDown<TlpTestCase>寫法中,看來將這種組合關(guān)系的定義放在花括號(hào)的前面就處理完畢是必要的。那么還有辦法嗎?我們想起,如果在TlpTestTearDown的成員方法中使用TlpTestCase類型,那么可以延遲對(duì)TlpTestCase完整定義的依賴時(shí)刻。于是我們將TlpTestTearDown的實(shí)現(xiàn)修改如下:
template<typename TestCase> struct TlpTestTeardown
{
TlpTestTeardown()
{
struct Teardown : TestCase
{
// 在這里實(shí)現(xiàn)teardown的定義,這里可以使用TestCase的內(nèi)部定義!
};
}
};
在上面的代碼中,我們?cè)?code>TlpTestTeardown模板的構(gòu)造函數(shù)里面定義了一個(gè)臨時(shí)類Teardown
繼承了模板的入?yún)estCase。在該臨時(shí)類里面我們可以定義teardown的具體內(nèi)容,它可以訪問TestCase類的內(nèi)部定義。由于現(xiàn)在該臨時(shí)類定義在TlpTestTeardown的成員函數(shù)里,所以struct TlpTestCase : TlpTestTearDown<TlpTestCase>
時(shí)刻并不需要TlpTestCase的完整定義,編譯器會(huì)將TlpTestTearDown的構(gòu)造函數(shù)的實(shí)例化放到當(dāng)前cpp文件的最后,這時(shí)已經(jīng)能夠看到TlpTestCase的完整定義了。
當(dāng)使用上面這種技術(shù)之后,TEST
宏定義修改如下:
#define TEST(name) struct UNIQUE_NAME(tlp_test_) :TlpTestSetup, TlpTestTearDown<UNIQUE_NAME(tlp_test_)>
于是定義測(cè)試用例的用法還和以前一樣,仍舊可以使用熟悉的花括號(hào)。然而teardown的定義卻會(huì)稍微的不統(tǒng)一:
#define TEARDOWN_BEGIN() \
template<typename TestCase> struct TlpTestTeardown \
{ \
TlpTestTeardown() \
{ \
struct Teardown : TestCase \
{
#define TEARDOWN_END() }; } };
可以看到對(duì)于teardown的定義將不能像setup和testcase那樣使用花括號(hào)了,而是使用TEARDOWN_BEGIN()
和TEARDOWN_END()
。雖然風(fēng)格有所差別,但這已經(jīng)是我能想到的最好辦法了。
為了讓setup的定義能夠和teardown統(tǒng)一,我們也給setup的定義提供了一套對(duì)稱的宏定義SETUP_BEGIN()
,SETUP_END()
。當(dāng)fixture中需要同時(shí)出現(xiàn)setup和teardown時(shí),setup可以使用和teardown一致的風(fēng)格去定義。其它情況下,setup還是使用花括號(hào)的風(fēng)格。
上述技術(shù)中其實(shí)還存在一個(gè)問題,TlpTestTeardown構(gòu)造函數(shù)中Teardown類繼承的TestCase是從模板參數(shù)輸入的,所以在Teardown中是不能直接引用TestCase中的定義的,必須加上TestCase::
前綴。為此我們實(shí)現(xiàn)了一個(gè)更有語(yǔ)義的宏__test_refer()
,用于顯示指定teardown中某一個(gè)變量引用自TestCase。
#define __test_refer(...) typename TestCase::__VA_ARGS__
最終,我們可以如下定義一個(gè)完整的fixture:
FIXTURE(TestWithSetUpAndTearDown)
{
SETUP_BEGIN()
using Expected = __int(2);
SETUP_END()
TEARDOWN_BEGIN()
ASSERT_EQ(__test_refer(Result), Expected);
TEARDOWN_END()
TEST("test1")
{
using Result = __add(__int(1), __int(1));
};
TEST("test2")
{
using Result = __div(__int(4), __int(2));
};
};
我們不得不承認(rèn),引入teardown后讓整個(gè)框架復(fù)雜了很多,而且風(fēng)格也趨向不一致。但好在對(duì)于具有不可變性的元編程來說,清理測(cè)試上下文的事情本就意義不大,所以一般在fixture中需要定義teardown的機(jī)會(huì)并不多。關(guān)鍵地我們沒有因?yàn)橐雝eardown的功能損害到原有定義testcase的簡(jiǎn)潔性。而且在沒有teardown的fixture中,setup仍舊可以像以前一樣使用花括號(hào)。所以絕大多數(shù)情況下,還是可以接受的!
不過我相信應(yīng)該還有更好的實(shí)現(xiàn)方式,不過限于本人能力,只能到此!如果你了解更好的實(shí)現(xiàn)方式,還請(qǐng)不吝賜教!