單元測試(unit testing),是指對軟件中的最小可測試單元進行檢查和驗證。對於單元測試中單元的含義,通常來講,要根據實際狀況去斷定其具體含義,如 C 語言中單元指一個函數,Java 裏單元指一個類,圖形化的軟件中能夠指一個窗口或一個菜單等。總的來講,單元就是人爲規定的最小的被測功能模塊。git
單元測試是在軟件開發過程當中要進行的最低級別的測試活動,軟件的獨立單元將在與程序的其餘部分相隔離的狀況下進行測試。github
1)在一個複雜的項目中添加某功能模塊時,能夠快捷的進行鍼對性測試,而不用將整個項目 Run 起來。web
2)能夠便捷的對某個具體方法進行測試。編程
iOS 開發(或者 MacOS、tvOS、watchOS 等)中,單元測試有多種方式,主要分爲 Xcode 提供的以及第三方測試框架這兩類:數組
Xcode自帶 XCTest:XCTest 是 Xcode自帶的單元測試工具,其前身是 OCUnit,隨着 Xcode 的發展,XCTest 已經愈來愈完善,功能也越強大。bash
第三方框架 GHUnit:GHUnit 是 GitHub 上著名的開源測試框架,可視化、開源、擴展等功能,讓其相比 XCTest 更增強大(如今的 XCTest 也很完善了,不過 GHUnit 比較老,如今已經中止維護,不建議使用) OCMock:OCMock 也是 Github 上的著名開源測試框架,用於 Mock、Stub,爲測試提供數據做假功能。框架
以上三種就是比較主流的測試 Xcode 單元測試途徑,還有一些 BDD 行爲測試框架:異步
什麼是 BDD BDD(Behavior Driven Development),即行爲驅動開發,是敏捷開發技術之一,經過天然語言定義系統行爲,以功能使用者的角度,編寫需求場景,且這些行爲描述能夠直接造成需求文檔,同時也是測試標準。async
其餘的,還有一些其餘測試工具、測試方式,如:函數
一、建立測試類
注意,全部測試類都繼承自 XCTestCase。
二、測試類的結構
默認建立的單元測試類爲一個 .m
文件,裏面包含了如下四個方法:
- (void)setUp
:在每一個測試用例開始前調用,能夠作一些測試準備工做,爲可選方法。- (void)tearDown
:在每一個測試用例結束後調用,能夠作一些測試收尾工做,爲可選方法。- (void)testExample
:默認建立的測試用例。- (void)testPerformanceExample
:性能測試方法。三、測試流程
當咱們默認執行測試時,系統找到全部的測試類,並執行每一個測試方法;咱們也能夠選擇性地執行某些測試而已,好比,在 scheme 中 disable 某個用例,或者直接在測試導航欄中每一個測試用例後面的運行按鈕,單獨執行某個測試。
默認流程以下:
上一個測試類 -> 當前類+ (void)setUp
-> [ - (void)setUp
-> 測試方法 -> - (void)tearDown
] (循環直至當前類測試方法所有執行完) -> 當前類 + (void)tearDown
-> 下一個測試類
四、測試方法
測試方法以 test 爲前綴,沒有參數,返回值爲 void,方法中用斷言來判斷測試的正確性:
- (void)testColorIsRed {
// Set up, call test subject API. (Code could be shared in setUp method.)
// Test logic and values, assertions report pass/fail to testing framework.
// Tear down. (Code could be shared in tearDown method.
}
- (void)testThatItDoesURLEncoding {
// given
NSString *searchQuery = @"$content$amp;?@";
HTTPRequest *request = [HTTPRequest requestWithURL:@"/search?q=%@", searchQuery];
// when
NSString *encodedURL = request.URL;
// then
XCTAssertEqualObjects(encodedURL, @"/search?q=%24%26%3F%40");
}
複製代碼
五、測試斷言
斷言通常由判斷條件、字符串 format、字符串參數組成,參數可選,在 XCTest 中,斷言有如下分類:
Specta 和 Expecta 都是出自 Github 做者 Orta 之手,他最出名的開源框架莫過於 Cocoapods。
Specta 是一個輕量級 BBD 測試框架,其爲 DSL (Domain-Specific Language) 模式,讓測試更加接近於天然語言描述,更加易懂。
一、主要有如下特色:
二、語法介紹
SpecBegin 聲明瞭一個測試類,SpecEnd 結束類聲明
describe (context) 塊聲明瞭一組實例
it (example/specify) 是一個單一的樣例
beforeAll 是一個執行於所有同級塊以前的塊,僅僅執行一次。afterAll 與beforeAll相反,是在所有同級塊以後執行的塊。僅僅執行一次。
beforeEach/afterEach,在每個同級塊執行的時候,都會執行一次,而beforeAll/afterAll僅僅會執行一次
it/waitUntil/done()。異步調用,注意完畢異步操做以後。必須調用done()函數。例如如下:
it(@"should do some stuff asynchronously", ^{
waitUntil(^(DoneCallback done) {
// Async example blocks need to invoke done() callback.
done();
});
});
複製代碼
注意:在describe局部使用sharedExamplesFor定義shared examples。可以在它做用域內覆蓋全局的shared examples。
三、通常使用:
SpecBegin(Car)
describe(@"Car", ^{
__block Car *car;
// Will be run before each enclosed it
beforeEach(^{
car = [Car new];
});
// Will be run after each enclosed it
afterEach(^{
car = nil;
});
// An actual test
it(@"should be red", ^{
expect(car.color).to.equal([UIColor redColor]);
});
describe(@"when it is started", ^{
beforeEach(^{
[car start];
});
it(@"should have engine running", ^{
expect(car.engine.running).to.beTruthy();
});
});
describe(@"move to", ^{
context(@"when the engine is running", ^{
beforeEach(^{
car.engine.running = YES;
[car moveTo:CGPointMake(42,0)];
});
it(@"should move to given position", ^{
expect(car.position).to.equal(CGPointMake(42, 0));
});
});
context(@"when the engine is not running", ^{
beforeEach(^{
car.engine.running = NO;
[car moveTo:CGPointMake(42,0)];
});
it(@"should not move to given position", ^{
expect(car.engine.running).to.beTruthy();
});
});
});
});
SpecEnd
複製代碼
上面例子對 Car 這個類作測試,經過多個上下文嵌套(describe/context),結合不一樣的條件(beforeEach),來做出不一樣的斷言(it);當咱們某個測試失敗時,咱們會收到一段很明確的錯誤信息,好比:汽車啓動後應該移動到指定位置這個用例測試失敗,那麼咱們會收到 Car move to when the engine is running should move to given position 這麼一段話。這樣很是接近天然語言的描述會讓咱們很快知道錯誤出在哪裏。
四、注意:
若是想用 SPEC_BEGIN 和 SPEC_END 替代 SpecBegin and SpecEnd,應該在引入頭文件以前寫上 #define SPT_CEDAR_SYNTAX
若是要使用 XCTest Resporter,那麼在 Test Scheme 中,把 SPTXCTestReporter 字段值改成 SPECTA_REPORTER_CLASS
把環境變量 SPECTA_SHUFFLE 設置爲 1 啓用測試拖拽(test shuffling)
Expecta 是基於 Objective-C/Cocoa 的斷言框架,XCTest 自帶的斷言 XCAssert 有好幾個基礎操做,不過基礎的斷言不太豐富,和 Specta 也沒有很適配。 Expecta 不同,將匹配過程從斷言中剝離開,能夠很好地適配 Specta 的 DSL 斷言塊。
一、Expecta 有如下幾個特色:
沒有類型限制,好比數值 1,並不用關心它是整形仍是浮點數
鏈式編程,可讀性高,如:expect(foo).notTo.equal(1)
反向匹配,斷言不匹配只需加上 .notTo 或者 .toNot,如:expect(x).notTo.equal(y)
延時匹配,能夠在鏈式表達式中加入 .will、.willNot、.after(interval) 等操做來延時匹配
可擴展,支持增長自定義匹配
二、基礎匹配 API:
expect(x).to.equal(y); // x 與 y 相等
expect(x).to.beIdenticalTo(y); // x 與 y 相等且內存地址相同
expect(x).to.beNil(); // x 爲 nil
expect(x).to.beTruthy(); // x 爲 true(非 0)
expect(x).to.beFalsy(); // x 爲 false(0 值)
複製代碼
三、異步匹配
describe(@"WebImage", ^{
beforeAll(^{
// 設置默認延時匹配時間
[Expecta setAsynchronousTestTimeout:2];
});
it(@"will not be nil", ^{
// 使用默認延時匹配
expect(webImage).willNot.beNil();
});
it(@"should equal 42 after 3 seconds", ^{
// 不使用默認延時匹配,手動設置爲3秒
expect(webImage).after(3).to.equal(42);
});
});
複製代碼
四、自定義使用
#import <Specta/Specta.h>
#import <Expecta/Expecta.h>
#import "ImageModel.h"
SpecBegin(ImageModel);
__block ImageModel *model;
beforeEach(^{
model = [[ImageModel alloc] initWithUrl:@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png" title:@"天空獨角馬" described:@"在黃色的沙漠裏,特別突兀"];
});
it(@"should not nil", ^{
expect(model).toNot.beNil();
});
it(@"equal", ^{
expect(model.url).to.equal(@"http://pic37.nipic.com/20140113/8800276_184927469000_2.png");
expect(model.title).to.equal(@"天空獨角馬");
expect(model.described).to.equal(@"在黃色的沙漠裏,特別突兀");
});
SpecEnd;
複製代碼
Expecta 和 Specta 須要配合使用,與 XCTest 同樣都是基於 XCTestCase 實現。在斷言的使用上,XCTest 太過死板,Expecta 和 Specta 則很靈活,能夠知足大部分場景需求。