-
什么是單元測(cè)試
單元測(cè)試,就是測(cè)試代碼"單元"的功能,以確保在任何可能的條件下達(dá)到預(yù)期目的的一種測(cè)試方法.單元(Unit)是代碼中一個(gè)可測(cè)試的邏輯部分. 單元測(cè)試可以幫助開發(fā)者找到錯(cuò)誤和崩潰原因,這也是蘋果拒絕上架的首要原因(crash了還上個(gè)毛架..)!
測(cè)試方法應(yīng)該要能夠響應(yīng)所有類型的輸入,包括有效輸入和無(wú)效輸入的情況,以確保單元能夠正常運(yùn)行,可以這么理解,在正常操作下要能獲得我想要的結(jié)果,在異常情況(空值,缺少參數(shù)..)等一些條件下不反回對(duì)象,甚至能夠?qū)﹀e(cuò)誤進(jìn)行處理和回應(yīng).無(wú)論開發(fā)者對(duì)單元進(jìn)行了什么更改,現(xiàn)有的測(cè)試方法都應(yīng)該能夠成功運(yùn)行,而新加的測(cè)試也應(yīng)該要成功運(yùn)行.所以測(cè)試方法很關(guān)鍵!
But.很多開發(fā)者對(duì)單元測(cè)試不太感興趣,雖然它十分整潔,可以驗(yàn)證許許多多的問(wèn)題,但是創(chuàng)建,維護(hù)卻需要花時(shí)間.如果要覆蓋所有的功能和使用場(chǎng)景的話.意味著投入精力會(huì)更多一點(diǎn).
小結(jié): 測(cè)試可以增加項(xiàng)目的穩(wěn)定性,減少錯(cuò)誤的發(fā)生.一個(gè)良好的測(cè)試可以極高地提升用戶的滿意度.
-
測(cè)試基礎(chǔ)概念
Xcode使用XCTest作為單元測(cè)試框架,在Xcode5之前,使用的是一個(gè)名為OCUnit的開源測(cè)試框架. XCTest就是對(duì)OCUnit的替代品,能夠更好地與XCode協(xié)作.
單元測(cè)試的概念中有四個(gè)層級(jí):- 測(cè)試套件(Test suite): 測(cè)試套件是項(xiàng)目中所有測(cè)試的集合,在Xcode中,測(cè)試套件作為一個(gè)獨(dú)立的對(duì)象存在.
測(cè)試用類例(Test case classes): 測(cè)試功能是存放在類當(dāng)中的,每個(gè)測(cè)試的例類通常是對(duì)應(yīng)一個(gè)單獨(dú)類來(lái)進(jìn)行測(cè)試.比如: 對(duì)Login類的測(cè)試 ,應(yīng)該由LoginTests類來(lái)完成,所有的單元測(cè)試類都必須要繼承XCTestCase類.
** 測(cè)試用例方法():** 測(cè)試用例類包含多個(gè)方法,用來(lái)測(cè)試類的各種功能.
斷言(Assertions): 斷言用于檢查結(jié)果是否符合預(yù)期,如果不符合,斷言則會(huì)失敗,并拋出失敗的原因.(調(diào)試神器)
創(chuàng)建項(xiàng)目的時(shí)候勾選 Include Unit Tests
成功創(chuàng)建之后,打開項(xiàng)目結(jié)構(gòu).系統(tǒng)幫我們生成了一個(gè) "項(xiàng)目名+Tests"的文件夾.已經(jīng)囊括了單元測(cè)試的 .m文件.
分析下 .m的結(jié)構(gòu)
<code>
#import <XCTest/XCTest.h>
#import "Person.h"
@interface UnitTestTests : XCTestCase
@end
@implementation UnitTestTests
- (void)setUp {
[super setUp];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
[super tearDown];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)testExample {
}
- (void)testPerformanceExample {
[self measureBlock:^{
}];
}
</code>
說(shuō)說(shuō)這里面的幾個(gè)方法.
測(cè)試用例類 包含了 setUp 和 tearDown 方法, 這兩個(gè)方法不屬于測(cè)試用例方法,他們是測(cè)試用例類的初始化和析構(gòu)方法.setUp里面存放的是所有對(duì)象的設(shè)置代碼,而tearDown里面存放的是諸如關(guān)閉文件,取消網(wǎng)絡(luò)請(qǐng)求等清理活動(dòng)的代碼.Xcode會(huì)依次調(diào)用 setUp,某個(gè)測(cè)試用例方法和tearDown方法.如果有多個(gè)測(cè)試方法的話,那么setUp和tearDown會(huì)在每調(diào)用一次測(cè)試方法的過(guò)程中調(diào)用一次!
testExample是示例測(cè)試方法
testPerformanceExample是性能測(cè)試的示例方法.
對(duì)于Xcode來(lái)說(shuō),測(cè)試分兩種:
a. 功能測(cè)試 :功能不符合預(yù)期會(huì)報(bào)錯(cuò).
b. 性能測(cè)試 :性能測(cè)試需要設(shè)置基準(zhǔn)線,一旦發(fā)現(xiàn)測(cè)試結(jié)果低于基準(zhǔn)線,或者超出最大標(biāo)準(zhǔn)差(STDDEV)限制,就會(huì)報(bào)告一個(gè)錯(cuò)誤.
PS:這里就不對(duì)性能測(cè)試做其他說(shuō)明了,有興趣可以自行搜集資料.
注意: 自定義的測(cè)試方法必須以 test開始,這樣Xcode才能找到. (左邊有圖標(biāo)則表明方法有效)
運(yùn)行測(cè)試
當(dāng)指向測(cè)試類和測(cè)試方法的時(shí)候,會(huì)出現(xiàn)一個(gè)按鈕,我們可以運(yùn)行所有的測(cè)試,也可以運(yùn)行某一個(gè)獨(dú)立的測(cè)試方法.測(cè)試結(jié)束后,Xcode將會(huì)返回成功或者是失敗結(jié)果
當(dāng)然,我們也可以選中我們想要運(yùn)行的測(cè)試類,然后選擇菜單欄的 product->performAction -> Run Test Methods來(lái)運(yùn)行選中的測(cè)試類.如果你只選擇了一個(gè)測(cè)試類的話,那么相應(yīng)的選項(xiàng)會(huì)變成 Run "測(cè)試方法名".
Xcode將自動(dòng)編譯并運(yùn)行應(yīng)用程序,然后執(zhí)行測(cè)試操作,測(cè)試操作完畢后,Xcode會(huì)退出應(yīng)用,短暫的顯示測(cè)試結(jié)果,同時(shí)測(cè)試結(jié)果會(huì)以相應(yīng)的圖標(biāo)表示測(cè)試是否成功!
在修改好我們發(fā)現(xiàn)的問(wèn)題之后,單擊失敗測(cè)試上的運(yùn)行按鈕,或者 選擇product->performAction -> TestAgian
回到我們的例子.
這是.m的實(shí)現(xiàn)文件
簡(jiǎn)單的給person賦值了一個(gè)name,age.
目前沒有對(duì)age做任何的判斷.意味著不論是給age 賦值任何 int類型的 值 都能夠成功!這當(dāng)然不符合我們的預(yù)期.
我們?cè)跍y(cè)試類里面可以開始動(dòng)我們的方法了.
// UnitTestTests.m
// UnitTestTests
//
// Created by uncle-R on 16/7/5.
// Copyright ? 2016年 uncle-R. All rights reserved.
//
#import <XCTest/XCTest.h>
#import "Person.h"
@interface UnitTestTests : XCTestCase
@property (nonatomic, strong) Person *p1;
@end
@implementation UnitTestTests
- (void)setUp {
[super setUp];
//給p1.賦值一個(gè) -1的age;
self.p1 = [[Person alloc]initWithName:@"小明" andAge:-1];
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
[super tearDown];
}
- (void)testAge{
//我們想要的結(jié)果是年齡不管怎樣都必須大于0.
XCTAssert(self.p1.age > 0 ,@"年齡必須大于0");
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
//[self ageTest];
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
結(jié)果如下
可見測(cè)試結(jié)果失敗.我們必須在person類的修改.
#import "Person.h"
@implementation Person
-(instancetype)initWithName:(NSString*)name andAge:(int)age{
if (self = [super init]) {
//對(duì)小于0的年齡做處理
if(age <= 0) age = 1;
self.age = age;
self.name = name;
}
return self;
}
@end
在運(yùn)行一次測(cè)試.
我們成功的通過(guò)了測(cè)試.
-
再談功能測(cè)試
功能測(cè)試的核心在于 "斷言",上面的例子不難看出,測(cè)試結(jié)果取決于斷言是否成功.
功能測(cè)試大致可以分如下幾種:
1. 基礎(chǔ)測(cè)試
2. 布爾測(cè)試
3.相等測(cè)試
4.空值測(cè)試
5.無(wú)條件失敗
6.測(cè)試實(shí)例
斷言:
|測(cè)試名稱| 斷言 | 特性 |
|: ---- :|:------:| ---- ---:|
| 基礎(chǔ)測(cè)試 | XCTAssert | 最基礎(chǔ)的斷言,表達(dá)式為假則測(cè)試失敗|
| 布爾測(cè)試 |XCTAssertTure, XCTAssertFalse | 基礎(chǔ)測(cè)試的擴(kuò)展,當(dāng)表達(dá)式結(jié)果和布爾測(cè)試不匹配時(shí)則失敗.
| 相等測(cè)試 | XCTAsserEqual,XCTAssertEqualObjects等 | 兩個(gè)表達(dá)式不相等則測(cè)試失敗 |
|空值測(cè)試 |XCTAssertNotNil |如果表達(dá)式為空則測(cè)試失敗|
| 無(wú)條件失敗| XCTFail | 總是測(cè)試失敗(運(yùn)行過(guò)這段代碼就失敗)|
雖然所有的測(cè)試都可以使用XCTAssert,但是Xcode還是提供了一些更有效的宏.當(dāng)沒有更好代替的情況下,才使用XCTAssert
格式:
** XCTAssert(表達(dá)式,消息)**
注意一下, 無(wú)條件失敗通常用在控制流中,比如一個(gè)if esle結(jié)構(gòu)中.if 里面都是正常的結(jié)果,else 正常情況下壓根就不會(huì)進(jìn)去,我們這時(shí)候可以往else丟一個(gè) XCTFail進(jìn)去,立馬就能檢測(cè)出異常.
當(dāng)測(cè)試失敗的時(shí)候
在發(fā)現(xiàn)測(cè)試失敗的時(shí)候,先檢查自己測(cè)試方法是不是存在問(wèn)題,因?yàn)椴⒉皇撬羞壿嬕婚_始都想通的,因?yàn)橛行r(shí)候測(cè)試失敗并不僅僅是測(cè)試對(duì)象屬性為nil,或者方法出問(wèn)題,還可能是因?yàn)闇y(cè)試方法存在某些不達(dá)標(biāo)的地方.
因此在測(cè)試之前,最好先想清楚測(cè)試的目的,再檢查自己測(cè)試的過(guò)程是否符合要求.
tip: 當(dāng)斷言過(guò)多的時(shí)候要看仔細(xì), 一個(gè)Not 就能毀掉整個(gè)測(cè)試.