前言:
對于單元測試來說,我想大部分同行,在項目中,很少會用到,也有一大部分,知道單元測試這個東西,但是確切的說沒有嘗試過,也不知道怎么回事,我想寫篇文章總結(jié)一下,了解一下單元測試。我也志在學習一下單元測試。如果觸碰到什么誤區(qū),希望大家多多提醒,幫助,謝謝。
我看了幾篇單元測試的文章,其中寫到單元測試多數(shù)用于:
1.調(diào)試接口是否正常使用。比如要測試一個網(wǎng)絡接口,通常每次都要重新啟動,經(jīng)過繁復的操作之后,才能測試到網(wǎng)絡接口。要是用單元測試,就可以直接測試那個方法,相對方便很多。
2.比如由于修改較多,想測試分享功能是否正常,(而不是重新啟動程序,進入到分享界面,點擊分享,填寫分享內(nèi)容。),在單元測試通過了,直接用到相應的地方。
3.自動發(fā)布、自動測試(特別在一些大的項目,以防止程序被誤改或引起新的問題)。
4.用戶注冊/登陸等
了解一下單元測試:
單元測試(Unit Testing)又稱為模塊測試,是針對程序模塊軟件設計來進行正確性檢驗的測試工作。程序單元是應用的最小可測試部件。對于面向?qū)ο缶幊蹋钚卧褪欠椒ǎɑ悺⒊橄箢悺⒒蛘吲缮愔械姆椒ā?/p>
通常來說,程序員每修改一次代碼就會修改某個單元,那我們就可以對這個單元做修改的驗證(單元測試),在編寫程序的過程中前后很可能要進行多次單元測試,以證實程序達到軟件規(guī)格書(產(chǎn)品需求)要求的工作目標,而且沒有程序錯誤。
每個理想的測試案例獨立于其它case,測試時需隔離模塊。單元測試通常由軟件開發(fā)人員編寫,用于確保所寫的代碼匹配軟件需求和遵循開發(fā)目標。它的實施方式可以是手動的,或是構(gòu)建自動化的一部分。
單元測試允許程序員在未來重構(gòu)代碼,且確保模塊依然工作正確。這個過程是為所有方法編寫單元測試,一旦變更導致錯誤發(fā)生,借助于單元測試可以快速定位并修復錯誤。
可讀性強的單元測試可以使程序員方便地檢查代碼片斷是否依然正常工作。良好設計的單元測試案例覆蓋程序單元分支和循環(huán)條件的所有路徑。在連續(xù)的單元測試環(huán)境,通過其固有的持續(xù)維護工作,單元測試可以延續(xù)用于準確反映當任何變更發(fā)生時可執(zhí)行程序和代碼的表現(xiàn)。借助于上述開發(fā)實踐和單元測試的覆蓋,可以總是維持準確性。
了解一下單元測試目的:
- 保證代碼的質(zhì)量 (幫助你編寫高質(zhì)量代碼、減少bu)
代碼可以通過編譯器檢查語法的正確性,卻不能保證代碼邏輯是正確的,尤其包含了許多單元分支的情況下,單元測試可以保證代碼的行為和結(jié)果與我們的預期和需求一致。在測試某段代碼的行為是否和你的期望一致時,你需要確認,在任何情況下,這段代碼是否都和你的期望一致,譬如參數(shù)可能為空,可能的異步操作等。
有一部分bug的原因是開發(fā)人員在編寫工作代碼的時候沒有考慮到某些case或者邊際條件。造成這種問題的原因很多,其中很重要的一個原因是我們對工作代碼所要完成的功能思考不足,而編寫單元測試,特別是先寫單元測試再寫工作代碼就可以幫助開發(fā)人員思考編寫的代碼到底要實現(xiàn)哪些功能。例如實現(xiàn)一個簡單的用戶注冊功能的業(yè)務類方法,用單元測試再寫工作代碼的方式來工作的話開發(fā)人員就會先考慮各種場景相關,例如正常注冊、用戶名重復、沒有滿足必要的填寫內(nèi)容......等等,之后就會編寫相關的測試用例。編寫單元測試代碼的過程就是促使開發(fā)人員思考工作代碼實現(xiàn)內(nèi)容和邏輯的過程,之后實現(xiàn)工作代碼的時候,開發(fā)人員思路會更清晰,實現(xiàn)代碼的質(zhì)量也會有相應的提升。
- 保證代碼的可維護性 (提升代碼的反饋速度,減少重復工作,保證你最后的代碼修改不會破壞之前代碼的功能)
保證原有單元測試正確的情況下,無論如何修改單元內(nèi)部代碼,測試的結(jié)果應該是正確的,且修改后不會影響到其他的模塊。
開發(fā)人員實現(xiàn)某個功能或者修補了某個bug,如果有相應的單元測試支持的話,開發(fā)人員可以馬上通過運行單元測試來驗證之前完成的代碼是否正確,而不需要反復通過編譯運行simulator、等待應用啟動、通過輸入數(shù)據(jù)等繁瑣的步驟來驗證所完成的功能。用單元測試代碼來驗證代碼和通過發(fā)布應用以人工的方式來驗證代碼這兩者的效率差很多,所以單元測試其實還能節(jié)約人力成本。
項目越做越大,代碼越來越多,特別涉及到一些公用接口之類的代碼或是底層的基礎庫,誰也不敢保證這次修改的代碼不會破壞之前的功能,所以與此相關的需求會被擱置或推遲,由于不敢改進代碼,代碼也變得越來越難以維護,質(zhì)量也越來越差。而單元測試就是解決這種問題的很好方法(不敢說最好的)。由于代碼的歷史功能都有相應的單元測試保證,修改了某些代碼以后,通過運行相關的單元測試就可以驗證出新調(diào)整的功能是否有影響到之前的功能。當然要實現(xiàn)到這種程度需要很大的付出,不但要能夠達到比較高的測試覆蓋率,而且單元測試代碼的編寫質(zhì)量也要有保證。
- 保證代碼的可擴展性
為了保證可行的可持續(xù)的單元測試,程序單元應該是低耦合的,否則,單元測試將難以進行,說明代碼的依賴性很高。
了解一下單元測試的本質(zhì):
- 是一種驗證行為
單元測試在開發(fā)前期檢驗了代碼邏輯的正確性,開發(fā)后期,無論是修改代碼內(nèi)部抑或重構(gòu),測試的結(jié)果為這一切提供了可量化的保障。
- 是一種設計行為
為了可進行單元測試,尤其是先寫單元測試(TDD),我們將從調(diào)用者思考,從接口上思考,我們必須把程序單元設計成接口功能劃分清晰的,易于測試的,且與外部模塊耦合性盡可能小。
- 是一種快速回歸的方式
在原代碼基礎上開發(fā)及修改功能時,單元測試是一種快捷,可靠的回歸。
除了那些大拿們編寫的代碼,我相信很多易于維護、設計良好的代碼都是通過不斷的重構(gòu)才得到的。雖然說單元測試本身不能直接改進生產(chǎn)代碼的質(zhì)量,但它為生產(chǎn)代碼提供了“安全網(wǎng)”,讓開發(fā)人員可以勇敢地改進代碼,從而讓代碼的clean和beautiful不再是夢想。
- 是程序優(yōu)良的文檔
從效果上而言,單元測試就像是能執(zhí)行的文檔,說明了在你用各種條件調(diào)用代碼時,你所能期望這段代碼完成的功能。
由于給代碼寫很多單元測試,相當于給代碼加上了規(guī)格說明書,開發(fā)人員通過讀單元測試代碼也能夠幫助開發(fā)人員理解現(xiàn)有代碼。很有Open Source的項目(如,AFNetworking, FMDB,喵神的VVDoucment等)都有相當量的單元測試代碼,通過讀這些測試代碼會有助于理解生產(chǎn)源代碼。
兩種測試思想
??測試驅(qū)動開發(fā)(Test-driven development,TDD)是一種軟件開發(fā)過程中的應用方法,由極限編程中倡導,以其倡導先寫測試程序,然后編碼實現(xiàn)其功能得名。測試驅(qū)動開發(fā)是戴兩頂帽子思考的開發(fā)方式:先戴上實現(xiàn)功能的帽子,在測試的輔助下,快速實現(xiàn)其功能;再戴上重構(gòu)的帽子,在測試的保護下,通過去除冗余的代碼,提高代碼質(zhì)量。測試驅(qū)動著整個開發(fā)過程:首先,驅(qū)動代碼的設計和功能的實現(xiàn);其后,驅(qū)動代碼的再設計和重構(gòu)。
行為驅(qū)動開發(fā)(Behavior-driven development,BDD)是一種敏捷軟件開發(fā)的技術,BDD的重點是通過與利益相關者的討論取得對預期的軟件行為的清醒認識。
它通過用自然語言書寫非程序員可讀的測試用例擴展了 測試驅(qū)動開發(fā)方法(TDD)
。這讓開發(fā)者得以把精力集中在代碼應該怎么寫,而不是技術細節(jié)上,而且也最大程度的減少了將代碼編寫者的技術語言與商業(yè)客戶、用戶、利益相關者、項目管理者等的領域語言之間來回翻譯的代價。在iOS單元測試框架中,kiwi是BDD的代表。
介紹
OCUnit(
即用XCTest進行測試
)其實就是蘋果自帶的測試框架。GHUnit是一個可視化的測試框架。
有了它,你可以點擊APP來決定測試哪個方法,并且可以點擊查看測試結(jié)果等。OCMock就是模擬某個方法或者屬性的返回值,你可能會疑惑為什么要這樣做?使用用模型生成的模型對象,再傳進去不就可以了?答案是可以的,但是有特殊的情況。比如你測試的是方法A,方法A里面調(diào)用到了方法B,而且方法B是有參數(shù)傳入,但又不是方法A所提供。這時候,你可以使用OCMock來模擬方法B返回的值。(在不影響測試的情況下,就可以這樣去模擬。)除了這些,在沒有網(wǎng)絡的情況下,也可以通過OCMock模擬返回的數(shù)據(jù)。
UITests就是通過代碼化來實現(xiàn)自動點擊界面,輸入文字等功能。靠人工操作的方式來覆蓋所有測試用例是非常困難的,尤其是加入新功能以后,舊的功能也要重新測試一遍,這導致了測試需要花非常多的時間來進行回歸測試,這里產(chǎn)生了大量重復的工作,而這些重復的工作有些是可以自動完成的,這時候UITests就可以幫助解決這個問題了。
案例 1
簡單的單元測試
1-1 創(chuàng)建一個新的項目
Snip20180619_12.png
1-2點開測試文件,進入到這個類
setUp :每個測試方法調(diào)用前執(zhí)行 tearDown :每個測試方法調(diào)用后執(zhí)行 testExample :是測試方法,和我們新建的沒有差別。 測試方法必須testXXX的格式,且不能有參數(shù),不然不會識別為測試方法 測試方法的執(zhí)行順序: 字典序排序。 快捷鍵:Command + U進行單元測試,這個快捷鍵是全部測試。
image.png
1-3在testExample方法中輸入如下:
NSLog(@"自定義測試testExample"); int a= 3; XCTAssertTrue(a == 0,"a 不能等于 0");
備注:紅色的叉子:代表測試未通過。綠色叉子:代表測試通過。
案例 2
案例 3
進行網(wǎng)絡請求的測試
使用CocoaPods安裝AFNetworking和STAlertView(CocoaPods安裝和使用教程 )
Pofile:platform :ios, '7.0' target 'UnitTestDemoTests' do pod 'AFNetworking', '~> 2.5.0' pod 'STAlertView', '~> 1.0.0' end target 'UnitTestDemoTestsTests' do pod 'AFNetworking', '~> 2.5.0' pod 'STAlertView', '~> 1.0.0' end
iOS9的http安全問題:現(xiàn)在進行異步請求的網(wǎng)絡測試,由于測試方法主線程執(zhí)行完就會結(jié)束,所以需要設置一下,否則沒法查看異步返回結(jié)果。
也可以在方法結(jié)束前設置等待,調(diào)回回來的時候再讓它繼續(xù)執(zhí)行。(另一種異步函數(shù)的單元測試)定義宏如下://waitForExpectationsWithTimeout是等待時間,超過了就不再等待往下執(zhí)行。 #define WAIT do {\ [self expectationForNotification:@"RSBaseTest" object:nil handler:nil];\ [self waitForExpectationsWithTimeout:30 handler:nil];\ } while (0); #define NOTIFY \ [[NSNotificationCenter defaultCenter]postNotificationName:@"RSBaseTest" object:nil];
增加測試方法:
-(void)testRequest{ // 1.獲得請求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; mgr.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/html",nil]; // 2.發(fā)送GET請求 [mgr GET:@"http://www.weather.com.cn/adat/sk/101110101.html" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) { NSLog(@"responseObject:%@",responseObject); XCTAssertNotNil(responseObject, @"返回出錯"); NOTIFY //繼續(xù)執(zhí)行 } failure:^(AFHTTPRequestOperation *operation, NSError *error) { NSLog(@"error:%@",error); XCTAssertNil(error, @"請求出錯"); NOTIFY //繼續(xù)執(zhí)行 }]; WAIT //暫停 }
有時候我們想測試一下整個流程是否可以跑通,比如獲取驗證碼、登錄、上傳頭像,查詢個人資料。其實只要輸入驗證碼就可以完成整個測試。這時候就需要用到輸入框了,以便程序繼續(xù)執(zhí)行。使用了一個第三方的彈出輸入框STAlertView,前面已經(jīng)設置。
STAlertView的使用方法:- (void)testAlertView { self.stAlertView = [[STAlertView alloc]initWithTitle:@"驗證碼" message:nil textFieldHint:@"請輸入手機驗證碼" textFieldValue:nil cancelButtonTitle:@"取消" otherButtonTitle:@"確定" cancelButtonBlock:^{ //點擊取消返回后執(zhí)行 [self testAlertViewCancel]; NOTIFY //繼續(xù)執(zhí)行 } otherButtonBlock:^(NSString *b) { //點擊確定后執(zhí)行 [self alertViewComfirm:b]; NOTIFY //繼續(xù)執(zhí)行 }]; [self.stAlertView show]; WAIT //設置等待時間 }
案例 4
測試的執(zhí)行順序
Snip20180619_1.png
通過上述測試得出結(jié)論:
可以看到無論我們怎樣調(diào)換test方法的書寫順序,其測試順序都是不變的。
目前初步的結(jié)論:測試方法執(zhí)行的順序與方法名中test后面的字符大小有關,小者優(yōu)先
,例如testA,testB1,testB2三個方法相繼執(zhí)行。
案例 5
Xcode集成了對
單元測試
的支持,XCode4.x集成的是OCUnit
,到了XCode5.x時代就升級為了XCTest
,XCode7.x時代XCtest還可以進行UI測試
。下面我們簡單介紹下XCTest
的使用。在xcode新建項目中,默認會建一個單元測試的target,并建立一個繼承于
XCTestCase
的測試用例類
image.png
?本例實現(xiàn)了一個個稅計算方法,在測試用例中測試輸入后輸出是否符合結(jié)果。
創(chuàng)建一個名為ASRevenueBL
的.h .m
文件,如下面所示:image.pngASRevenueBL.h
#import <Foundation/Foundation.h> @interface ASRevenueBL : NSObject - (double)calculate:(double)revenue; @end
ASRevenueBL.m
import "ASRevenueBL.h" #define baseNum 3500.0 // 起征點 @implementation ASRevenueBL /* * method:傳入收入計算稅值 * revenue:收入 */ - (double)calculate:(double)revenue { double tax = 0.0; // 稅 // 應納稅所得額 = 工資收入金額 - 各項社會保險費 - 起征點(3500元) // 應納稅額 = 應納稅所得額 x 稅率 - 速算扣除數(shù) double dbTaxRevenue = revenue - baseNum; if(dbTaxRevenue <= 1500){ tax = dbTaxRevenue * 0.03; } else if (dbTaxRevenue > 1500 && dbTaxRevenue <= 4500){ tax = dbTaxRevenue *0.1 -105; } else if (dbTaxRevenue > 4500 && dbTaxRevenue <= 9000){ tax = dbTaxRevenue * 0.2 - 555; }else if (dbTaxRevenue > 9000 && dbTaxRevenue <= 35000) { tax = dbTaxRevenue * 0.25 - 1005; } else if (dbTaxRevenue > 35000 && dbTaxRevenue <= 55000) { tax = dbTaxRevenue * 0.3 - 2755; } else if (dbTaxRevenue > 55000 && dbTaxRevenue <= 80000) { tax = dbTaxRevenue * 0.35 - 5505; } else if (dbTaxRevenue > 80000) { tax = dbTaxRevenue * 0.45 - 13505; } return tax; }
導入測試方法所在的類的頭文件,并創(chuàng)建一個類,在測試方法調(diào)用前,初始化類對象,測試完畢后,將對象置nil,其方法測試如下方測試代碼:
#import <XCTest/XCTest.h> #import "ASRevenueBL.h" @interface UnitTestsTwoTests : XCTestCase @property (nonatomic, strong) ASRevenueBL *revenueBL; @end @implementation UnitTestsTwoTests - (void)setUp { [super setUp]; self.revenueBL = [[ASRevenueBL alloc] init]; } - (void)tearDown { self.revenueBL = nil; [super tearDown]; } - (void)testLevel1 { double revenue = 5000; double tax = [self.revenueBL calculate:revenue]; XCTAssertEqual(tax, 45.0,@"測試案例1失敗"); XCTAssertTrue(tax == 45.0); } - (void)testLevel2 { XCTestExpectation *exp = [self expectationWithDescription:@"超時"]; NSOperationQueue *queue = [[NSOperationQueue alloc]init]; [queue addOperationWithBlock:^{ double revenue = 1500; double tax = [self.revenueBL calculate:revenue]; sleep(1); NSLog(@"%f",tax); XCTAssertEqual(tax, 45, @"用例2測試失敗"); [exp fulfill]; // exp結(jié)束 }]; [self waitForExpectationsWithTimeout:3 handler:^(NSError * _Nullable error) { if (error) { NSLog(@"Timeout Error: %@", error); } }]; } - (void)testExample { } - (void)testPerformanceExample { [self measureBlock:^{ for (int a = 0; a<10; a+=a) { NSLog(@"%zd", a); } }]; } @end
testLevel1
通過revenueBL
計算出來的tax與預期相同,測試通過;testLevel2
通過revenueBL
計算出來的tax與預期不同,測試不通過,反映出了程序一些邏輯漏洞;testPerformanceExample
中的平均執(zhí)行時間比基準值低,測試通過。
案例 6 命令行測試
在命令行中也可以啟動測試,便于持續(xù)集成。
Assuner$ cd Desktop/ Desktop Assuner$ cd ASUnitTestFirstDemo/ ASUnitTestFirstDemo Assuner$ xcodebuild test -project ASUnitTestFirstDemo.xcodeproj -scheme ASUnitTestFirstDemo -destination 'platform=iOS Simulator,OS=11.4,name=iPhone 7' // 可以有多個destination
結(jié)果
Test Suite 'All tests' started at 2017-09-11 11:12:16.348 Test Suite 'ASUnitTestFirstDemoTests.xctest' started at 2017-09-11 11:12:16.349 Test Suite 'ASUnitTestFirstDemoTests' started at 2017-09-11 11:12:16.349 Test Case '-[ASUnitTestFirstDemoTests testLevel1]' started. Test Case '-[ASUnitTestFirstDemoTests testLevel1]' passed (0.001 seconds). Test Case '-[ASUnitTestFirstDemoTests testLevel2]' started. /Users/liyongguang-eleme-iOS-Development/Desktop/ASUnitTestFirstDemo/ASUnitTestFirstDemoTests/ASUnitTestFirstDemoTests.m:46: error: -[ASUnitTestFirstDemoTests testLevel2] : ((tax) equal to (45.0)) failed: ("-60") is not equal to ("45") - 用例2測試失敗 Test Case '-[ASUnitTestFirstDemoTests testLevel2]' failed (1.007 seconds). Test Suite 'ASUnitTestFirstDemoTests' failed at 2017-09-11 11:12:17.358. Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.009) seconds Test Suite 'ASUnitTestFirstDemoTests.xctest' failed at 2017-09-11 11:12:17.359. Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.010) seconds Test Suite 'All tests' failed at 2017-09-11 11:12:17.360. Executed 2 tests, with 1 failure (0 unexpected) in 1.008 (1.012) seconds Failing tests: -[ASUnitTestFirstDemoTests testLevel2] ** TEST FAILED **
如果是workspace
xcodebuild -workspace ASKiwiTest.xcworkspace -scheme ASKiwiTest-Example -destination 'platform=iOS Simulator,OS=11.0,name=iPhone 7' test
每個test方法都會跑一遍,并給出結(jié)果描述。
案例 7 代碼的執(zhí)行時間測試-(性能測試)
性能測試
主要使用measureBlock
方法 ,用于測試一組方法的執(zhí)行時間
,通過設置baseline(基準)
和stddev(標準偏差)
來判斷方法是否能通過性能測試
。
假如直接執(zhí)行方法,因為block中沒有內(nèi)容,所以方法的執(zhí)行時間為0.0s,如果我們把baseline設成0.05,偏差10%,是可以通過的測試的。但是如果設置如果我們把baseline為1,偏差10%,那測試會失敗,因為不滿足條件。如上圖所示,這個方法是用來
測試block內(nèi)代碼的執(zhí)行時間
的,我們可以通過打印
很清楚的看到它其實執(zhí)行了10次
,用處也很寬廣,比如想測試身份證的識別時間,請求的時間,轉(zhuǎn)模型的速度
等等都可以通過它來測試
,這里只是舉個簡單的例子.我們可以看下打印發(fā)現(xiàn)他確實是執(zhí)行了十次.
再來看看左邊的執(zhí)行代碼相關信息,這里由于打印"1"執(zhí)行的太快無法看出效果
,所以我將測試內(nèi)容換成了使用for循環(huán)打印1-9999
,看看他們的執(zhí)行時間
.
可以很清楚的看到,10次的平均時間是1.382秒
,第一次時間是1.85秒
,并且可以看到第一次執(zhí)行時間超過了平均時間33%
,這里的測試結(jié)果都是和機器性能
有關系的.
案例 8 登陸模塊測試
案例 9 加法測試
- (void)testExample { //設置變量和設置預期值 NSUInteger a = 10;NSUInteger b = 15; NSUInteger expected = 24; //執(zhí)行方法得到實際值 NSUInteger actual = [self add:a b:b]; //斷言判定實際值和預期是否符合 XCTAssertEqual(expected, actual,@"add方法錯誤!"); } -(NSUInteger)add:(NSUInteger)a b:(NSUInteger)b{ return a+b; }
從這也能看出一個測試用例比較規(guī)范的寫法,1:定義變量和預期,2:執(zhí)行方法得到實際值,3:斷言
案例 10 代碼來自于AFNetworking,用于測試backgroundImageForState方法
- (void)testThatBackgroundImageChanges { XCTAssertNil([self.button backgroundImageForState:UIControlStateNormal]); NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(UIButton * _Nonnull button, NSDictionary<NSString *,id> * _Nullable bindings) { return [button backgroundImageForState:UIControlStateNormal] != nil; }]; [self expectationForPredicate:predicate evaluatedWithObject:self.button handler:nil]; [self waitForExpectationsWithTimeout:20 handler:nil]; }
利用謂詞計算,button是否正確的獲得了backgroundImage,如果正確20秒內(nèi)正確獲得則通過測試,否則失敗。
expectationForNotification
方法 ,該方法監(jiān)聽一個通知,如果在規(guī)定時間內(nèi)正確收到通知則測試通過。- (void)testAsynExample1 { [self expectationForNotification:(@"監(jiān)聽通知的名稱xxx") object:nil handler:nil]; [[NSNotificationCenter defaultCenter]postNotificationName:@"監(jiān)聽通知的名稱xxx" object:nil]; //設置延遲多少秒后,如果沒有滿足測試條件就報錯 [self waitForExpectationsWithTimeout:3 handler:nil]; }
這個例子也可以用
expectationWithDescription
實現(xiàn),只是多些很多代碼而已,但是這個可以幫助你更好的理解expectationForNotification
方法和expectationWithDescription
的區(qū)別。同理,expectationForPredicate
方法也可以使用expectationWithDescription
實現(xiàn)。func testAsynExample1() { let expectation = expectationWithDescription("監(jiān)聽通知的名稱xxx") let sub = NSNotificationCenter.defaultCenter().addObserverForName("監(jiān)聽通知的名稱xxx", object: nil, queue: nil) { (not) -> Void in expectation.fulfill() } NSNotificationCenter.defaultCenter().postNotificationName("監(jiān)聽通知的名稱xxx", object: nil) waitForExpectationsWithTimeout(1, handler: nil) NSNotificationCenter.defaultCenter().removeObserver(sub) }
XCTest常見的斷言
XCTFail(format...) 生成一個失敗的測試 XCTAssertNil(a1,format...)為空判斷, a1為空時通過,反之不通過; XCTAssertNotNil(a1,format...) 不為空判斷,a1不為空時通過,反之不通過; XCTAssert(expression,format...) 當expression求值為true時通過; XCTAssertTrue(expression,format...) 當expression求值為true時通過; XCTAssertFalse(expression,format...) 當expression求值為False時通過; XCTAssertEqualObjects(a1, a2,format...) 判斷相等 [a1 isEqual:a2]值為TRUE時通過,其中一個不為空時,不通過; XCTAssertNotEqualObjects(a1, a2,format...) 判斷不等,[a1 isEqual:a2]值為False時通過; XCTAssertEqual(a1, a2, format...)判斷相等(當a1和a2是 C語言標量、結(jié)構(gòu)體或聯(lián)合體時使用,實際測試發(fā)現(xiàn)NSString也可以); XCTAssertNotEqual(a1, a2, format...)判斷不等(當a1和a2是 C語言標量、結(jié)構(gòu)體或聯(lián)合體時使用); XCTAssertEqualWithAccuracy(a1, a2, accuracy, format...)判斷相等,(double或float類型)提供一個誤差范圍,當在誤差范圍(+/-accuracy)以內(nèi)相等時通過測試; XCTAssertNotEqualWithAccuracy(a1, a2, accuracy, format...) 判斷不等,(double或float類型)提供一個誤差范圍,當在誤差范圍以內(nèi)不等時通過測試; XCTAssertThrows(expression, format...)異常測試,當expression發(fā)生異常時通過;反之不通過;(很變態(tài)) XCTAssertThrowsSpecific(expression, specificException, format...) 異常測試,當expression發(fā)生specificException異常時通過;反之發(fā)生其他異常或不發(fā)生異常均不通過; XCTAssertThrowsSpecificNamed(expression, specificException, exception_name, format...)異常測試,當expression發(fā)生具體異常、具體異常名稱的異常時通過測試,反之不通過; XCTAssertNoThrow(expression, format…)異常測試,當expression沒有發(fā)生異常時通過測試; XCTAssertNoThrowSpecificNamed(expression, specificException, exception_name, format...)異常測試,當expression沒有發(fā)生具體異常、具體異常名稱的異常時通過測試,反之不通過
特別注意下XCTAssertEqualObjects和XCTAssertEqual。 XCTAssertEqualObjects(a1, a2, format...)的判斷條件是[a1 isEqual:a2]是否返回一個YES。XCTAssertEqual(a1, a2, format...)的判斷條件是a1 == a2是否返回一個YES。對于后者,如果a1和a2都是基本數(shù)據(jù)類型變量,那么只有a1 == a2才會返回YES
備注:
1.關于
私有方法
的測試,只能
通過擴展
來實現(xiàn)2.關于case的方法名字,一定要以
test開頭
并注意駝峰命名法
,且不能加入?yún)?shù)
。3.單元測試類繼承自
XCTestCase
,他有一些重要的方法,其中最重要的有3個,setUp ,tearDown,measureBlock
.4.
md + 5
切換到測試選項卡后會看到很多小箭頭
,點擊可以單獨
或整體測試.
5.
cmd + U
運行整個單元測試
6.使用
pod
的項目中,在XC測試框架中測試內(nèi)容包括第三方包時
,需要手動去設置Header Search Paths
才能找到頭文件 ,還需要設置test target
的PODS_ROOT
。7.xcode7要使用真機做跑測試時,證書必須配對,否則會報錯
exc_breakpoint
錯誤
部分案例Demo:
Demo-1 Demo-2
參考文章:
1-1 (一) 初探 iOS 單元測試
1-2 iOS單元測試(作用及入門提升)
1-3 愛上iOS單元測試系列之愛上她就要先了解她:單元測試入門
1-4 Testing with Xcode文檔(中文版):從 OCUnit 過渡到 XCTest
1-5 iOS關于單元測試的學習筆記還有本人在學的路上遇到的那些 坑
1-6 Xcode 5 單元測試(一)使用XCTest進行單元測試
1-7 簡單iOS單元測試-異步測試(XCTestExpectation)
1-8 cocoaChina測試專題
1-9 iOS單元測試