編寫單元測試與編寫工程程式碼略有不同。我們需要準備資料,mock物件,呼叫工程Api,驗證結果。而且一般測試程式碼都會比工程程式碼要大。就像Real-World Testing with XCTest一文中提到“目前為止,我們的編碼庫已經縱橫 190 個檔案和 18,000 行程式碼,達到了 544 kB。我們測試部分的程式碼現在差不多有1,200 kB,大概有被測試程式碼的兩倍”。那麼應該如何定義單元測試程式碼編寫規範,使得程式碼更整潔,可讀性更高呢?
為了工程程式碼的保密,文中的程式碼在命名的時候在不影響理解的情況下都做了改動
1.Given-When-Then分段
每個case其實都可以分為三步走,1.mock物件,準備測試資料。2.呼叫目標API 3.驗證輸出和行為。所以我們可以用如下方式將3步分別放入Given-When-Then三個分段中。(為了保密,程式碼做了改動)
1 2 3 4 5 6 7 8 9 10 11 |
- (void)testNeedToShowRowWhenTypeIsAll{ //given _sut.collectionType = 1; NSIndexPath *path1 = [NSIndexPath indexPathForRow:1 inSection:1]; //when BOOL needToShow1 = [_sut needToShowRow:path1]; //then assertThatBool(needToShow1,isTrue()); } |
這樣我們一眼掃過去就可以清晰的看出一個case大體上都在幹什麼。
2.一個Case只測試一種情況
可能我們呼叫的一個API內部有一個if…else…。我建議if一個case,else一個case。分兩個不同的case來作測試.這樣每個case就很清晰自己在測試什麼東西。而如果全部雜糅在一個case中,可讀性會降低不少,而且case體積也會變得相對大很多,因為你要Given-When-Then兩次。更不建議在case中寫for迴圈驗證。有人說我的測試目標函式中有很多if…else…,那麼我覺得你應該重構下你的設計了。
所以,我們的結論是一個Case只測試一種情況,不同情況用When標明:
1 2 3 4 5 6 7 |
- (void)testNeedToShowRowWhenTypeIsAll{ ... } - (void)testNeedToShowRowWhenTypeIsOnSell{ ... } |
3.用_sut來標明被測試類
一個測試檔案只有一個被測試類。但是當我們的測試檔案越來越多的時候,當我們看一個測試case的時候需要看懂這個case才明白我們的被測試類是誰。或者我們也可以看測試檔名(XXXXXXTest.m)才知道我們的被測試類是誰,但是這樣卻不是很直觀。所以不管我們在那個測試檔案中,測試的類是誰,叫什麼名字,我們都以為一個區域性變數名_sut來定義我們的被測試類。這樣我們一眼就能知道我們被測試類是誰。
_sut就是System Under Test的縮寫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@implementation JHSCollectionDataSourceTest { JHSCollectionDataSource *_sut; } - (void)testNeedToShowHeaderWhenTypeIsAll{ //given _sut.collectionType = 1; //when BOOL needToShow1 = [_sut needToShowHeader:1]; //then assertThatBool(needToShow1,isTrue()); } |
4.用Category暴露私有函式和屬性
我們的測試case中呼叫的方法可能會改變一個私有的屬性,呼叫一個私有的方法。怎麼去優雅的驗證這種行為呢,我們可以在測試檔案的開頭用一個名字為UnitTest的category來暴露出我們的私有方法和屬性(屬性暴露的是屬性對應的getter和setter方法)。
1 2 3 4 |
@interface JHSTestDataSource (UnitTest) - (NSInteger)getSellGroupCount; - (BOOL)needShowHeader:(NSInteger)section; @end |
總結
enjoy it!