編寫單元測試與編寫工程代碼略有不同。我們需要准備數據,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三個分段中。(為了保密,代碼做了改動)
- (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標明:
- (void)testNeedToShowRowWhenTypeIsAll{ ... } - (void)testNeedToShowRowWhenTypeIsOnSell{ ... }
3.用_sut來標明被測試類
一個測試文件只有一個被測試類。但是當我們的測試文件越來越多的時候,當我們看一個測試case的時候需要看懂這個case才明白我們的被測試類是誰。或者我們也可以看測試文件名(XXXXXXTest.m)才知道我們的被測試類是誰,但是這樣卻不是很直觀。所以不管我們在那個測試文件中,測試的類是誰,叫什麼名字,我們都以為一個局部變量名_sut來定義我們的被測試類。這樣我們一眼就能知道我們被測試類是誰。
_sut就是System Under Test的縮寫。
@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方法)。
@interface JHSTestDataSource (UnitTest) - (NSInteger)getSellGroupCount; - (BOOL)needShowHeader:(NSInteger)section; @end
總結
enjoy it!