在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ iOS/ 測(cè)試 View Controllers
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類(lèi)型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類(lèi)
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶(hù)端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類(lèi)
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線(xiàn)程安全類(lèi)的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴(lài)注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴(lài)注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

測(cè)試 View Controllers

我們不是迷信測(cè)試,但它應(yīng)該幫助我們加快開(kāi)發(fā)進(jìn)度,并且讓事情變得更有趣。

讓事情保持簡(jiǎn)單

測(cè)試簡(jiǎn)單的事情很簡(jiǎn)單,同樣,測(cè)試復(fù)雜的事會(huì)很復(fù)雜。就像我們?cè)谄渌恼轮兄赋龅哪菢?,讓事情保持?jiǎn)單小巧總是好的。除此之外,它還有利于我們測(cè)試。這是件雙贏的事。讓我們來(lái)看看測(cè)試驅(qū)動(dòng)開(kāi)發(fā)(簡(jiǎn)稱(chēng) TDD),有些人喜歡它,有些人則不喜歡。我們?cè)谶@里不深入討論,只是如果用 TDD,你得在寫(xiě)代碼之前先寫(xiě)好測(cè)試。如果你好奇的話(huà),可以去找 Wikipedia 上的文章看看。同時(shí),我們也認(rèn)為重構(gòu)和測(cè)試可以很好地結(jié)合在一起。

測(cè)試 UI 部分通常很麻煩,因?yàn)樗鼈儼嗷顒?dòng)部件。通常,view controller 需要和大量的 model 和 view 類(lèi)交互。為了使 view controller 便于測(cè)試,我們要讓任務(wù)盡量分離。

幸好,我們?cè)?a rel="nofollow" >更輕量的 view controller 這篇文章中的闡述的技術(shù)可以讓測(cè)試更加簡(jiǎn)單。通常,如果你發(fā)現(xiàn)有些地方很難做測(cè)試,這就說(shuō)明你的設(shè)計(jì)出了問(wèn)題,你應(yīng)該重構(gòu)它。你可以重新參考更輕量的 view controller 這篇文章來(lái)獲得一些幫助??偟哪繕?biāo)就是有清晰的關(guān)注點(diǎn)分離。每個(gè)類(lèi)只做一件事,并且做好。這樣就可以讓你只測(cè)試這件事。

記?。簻y(cè)試越多,回報(bào)的增長(zhǎng)趨勢(shì)越慢。首先你應(yīng)該做簡(jiǎn)單的測(cè)試。當(dāng)你覺(jué)得滿(mǎn)意時(shí),再加入更多復(fù)雜的測(cè)試。

Mocking

當(dāng)你把一個(gè)整體拆分成小零件(比如更小的類(lèi))時(shí),我們可以針對(duì)每個(gè)小的類(lèi)來(lái)進(jìn)行測(cè)試。但由于我們測(cè)試的類(lèi)會(huì)和其他類(lèi)交互,這里我們用一個(gè)所謂的 mockstub 來(lái)繞開(kāi)它。把 mock 對(duì)象看成是一個(gè)占位符,我們測(cè)試的類(lèi)會(huì)跟這個(gè)占位符交互,而不是真正的那個(gè)對(duì)象。這樣,我們就可以針對(duì)性地測(cè)試,并且保證不依賴(lài)于應(yīng)用程序的其他部分。

在示例程序中,我們有個(gè)包含數(shù)組的 data source 需要測(cè)試。這個(gè) data source 會(huì)在某個(gè)時(shí)候從 table view 中取出(dequeue)一個(gè) cell。在測(cè)試過(guò)程中,還沒(méi)有 table view,但是我們傳遞一個(gè) mock 的 table view,這樣即使沒(méi)有 table view,也可以測(cè)試 data source,就像下面你即將看到的。起初可能有點(diǎn)難以理解,多看幾次后,你就能體會(huì)到它的強(qiáng)大和簡(jiǎn)單。

Objective-C 中有個(gè)用來(lái) mocking 的強(qiáng)大工具叫做 OCMock。它是一個(gè)非常成熟的項(xiàng)目,充分利用了 Objective-C 運(yùn)行時(shí)強(qiáng)大的能力和靈活性。它使用了一些很酷的技巧,讓通過(guò) mock 對(duì)象來(lái)測(cè)試變得更加有趣。

本文后面有 data source 測(cè)試的例子,它更加詳細(xì)地展示了這些技術(shù)如何工作在一起。

SenTestKit

編者注 這一節(jié)有一些過(guò)時(shí)了。在 Xcode 5 中 SenTestingKit 已經(jīng)被 XCTest 完全取代,不過(guò)兩者使用上沒(méi)有太多區(qū)別,我們可以通過(guò) Xcode 的 Edit -> Refactor -> Convert to XCTest 選項(xiàng)來(lái)切換到新的測(cè)試框架

我們將要使用的另一個(gè)工具是一個(gè)測(cè)試框架,開(kāi)發(fā)者工具的一部分:Sente 的 SenTestingKit。這個(gè)上古神器從 1997 年起就伴隨在 Objective-C 開(kāi)發(fā)者左右,比第一款 iPhone 發(fā)布還早 10 年?,F(xiàn)在,它已經(jīng)集成到 Xcode 中了。SenTestingKit 會(huì)運(yùn)行你的測(cè)試。通過(guò) SenTestingKit,你將測(cè)試組織在類(lèi)中。你需要給每一個(gè)你想測(cè)試的類(lèi)創(chuàng)建一個(gè)測(cè)試類(lèi),類(lèi)名以 Tests 結(jié)尾,它反應(yīng)了這個(gè)類(lèi)是干什么的。

這些測(cè)試類(lèi)里的方法會(huì)做具體的測(cè)試工作。方法名必須以 test 開(kāi)頭來(lái)作為觸發(fā)一個(gè)測(cè)試運(yùn)行的條件。還有特殊的 -setUp-tearDown 方法,你可以重載它們來(lái)設(shè)置各個(gè)測(cè)試。記住,你的測(cè)試類(lèi)就是個(gè)類(lèi)而已:只要對(duì)你有幫助,可以按需求在里面加 properties 和輔助方法。

做測(cè)試時(shí),為測(cè)試類(lèi)創(chuàng)建基類(lèi)是個(gè)不錯(cuò)的模式。把通用的邏輯放到基類(lèi)里面,可以讓測(cè)試更簡(jiǎn)單和集中??梢酝ㄟ^(guò)示例程序中的例子來(lái)看看這樣帶來(lái)的好處。我們沒(méi)有使用 Xcode 的測(cè)試模板,為了讓事情簡(jiǎn)單有效,我們只創(chuàng)建了單獨(dú)的 .m 文件。通過(guò)把類(lèi)名改成以 Tests 結(jié)尾,類(lèi)名可以反映出我們?cè)趯?duì)什么做測(cè)試。

編者注 Xcode 5 中 默認(rèn)的測(cè)試模板也不再會(huì)自動(dòng)創(chuàng)建 .h 文件了

與 Xcode 集成

測(cè)試會(huì)被 build 成一個(gè) bundle,其中包含一個(gè)動(dòng)態(tài)庫(kù)和你選擇的資源文件。如果你要測(cè)試某些資源文件,你得把它們加到測(cè)試的 target 中,Xcode 就會(huì)將它們打包到一個(gè) bundle 中。接著你可以通過(guò) NSBundle 來(lái)定位這些資源文件,示例項(xiàng)目實(shí)現(xiàn)了一個(gè) -URLForResource:withExtension: 方法來(lái)方便的使用它。

Xcode 中的每個(gè) scheme 定義了相應(yīng)的測(cè)試 bundle 是哪個(gè)。通過(guò) ?-R 運(yùn)行程序,?-U 運(yùn)行測(cè)試。

測(cè)試的運(yùn)行依附于程序的運(yùn)行,當(dāng)程序運(yùn)行時(shí),測(cè)試 bundle 將被注入(injected)。測(cè)試時(shí),你可能不想讓你的程序做太多的事,那樣會(huì)對(duì)測(cè)試造成干擾??梢园严旅娴拇a加到 app delegate 中:

static BOOL isRunningTests(void) __attribute__((const));

- (BOOL)application:(UIApplication *)application
        didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    if (isRunningTests()) {
        return YES;
    }

    //
    // Normal logic goes here
    //

    return YES;
}

static BOOL isRunningTests(void)
{
    NSDictionary* environment = [[NSProcessInfo processInfo] environment];
    NSString* injectBundle = environment[@"XCInjectBundle"];
    return [[injectBundle pathExtension] isEqualToString:@"octest"];
}

編輯 Scheme 給了你極大的靈活性。你可以在測(cè)試之前或之后運(yùn)行腳本,也可以有多個(gè)測(cè)試 bundle。這對(duì)大型項(xiàng)目來(lái)說(shuō)很有用。最重要的是,可以打開(kāi)或關(guān)閉個(gè)別測(cè)試,這對(duì)調(diào)試測(cè)試非常有用,只是要記得之后再把它們重新全部打開(kāi)。

還要記住你可以為測(cè)試代碼下斷點(diǎn),當(dāng)測(cè)試執(zhí)行時(shí),調(diào)試器會(huì)在斷點(diǎn)處停下來(lái)。

測(cè)試 Data Source

好了,讓我們開(kāi)始吧。我們已經(jīng)通過(guò)拆分 view controller 讓測(cè)試工作變得更輕松了?,F(xiàn)在我們要測(cè)試 ArrayDataSource。首先我們新建一個(gè)空的,基本的測(cè)試類(lèi)。我們把接口和實(shí)現(xiàn)都放到一個(gè)文件里;也沒(méi)有哪個(gè)地方需要包含 @interface,放到一個(gè)文件會(huì)顯得更加漂亮和整潔。

#import "PhotoDataTestCase.h"

@interface ArrayDataSourceTest : PhotoDataTestCase
@end

@implementation ArrayDataSourceTest
- (void)testNothing;
{
    STAssertTrue(YES, @"");
}
@end

這個(gè)類(lèi)沒(méi)做什么事,只是展示了基本的設(shè)置。當(dāng)我們運(yùn)行這個(gè)測(cè)試時(shí),-testNothing 方法將會(huì)運(yùn)行。特別地,STAssert 宏將會(huì)做瑣碎的檢查。注意,前綴 ST 源自于 SenTestingKit。這些宏和 Xcode 集成,會(huì)把失敗顯示到側(cè)邊面板的 Issues 導(dǎo)航欄中。

第一個(gè)測(cè)試

我們現(xiàn)在把 testNothing 替換成一個(gè)簡(jiǎn)單、真正的測(cè)試:

- (void)testInitializing;
{
    STAssertNil([[ArrayDataSource alloc] init], @"Should not be allowed.");
    TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){};
    id obj1 = [[ArrayDataSource alloc] initWithItems:@[]
                                      cellIdentifier:@"foo"
                                  configureCellBlock:block];
    STAssertNotNil(obj1, @"");
}

實(shí)踐 Mocking

接著,我們想測(cè)試 ArrayDataSource 實(shí)現(xiàn)的方法:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath;

為此,我們創(chuàng)建一個(gè)測(cè)試方法:

- (void)testCellConfiguration;

首先,創(chuàng)建一個(gè) data source:

__block UITableViewCell *configuredCell = nil;
__block id configuredObject = nil;
TableViewCellConfigureBlock block = ^(UITableViewCell *a, id b){
    configuredCell = a;
    configuredObject = b;
};
ArrayDataSource *dataSource = [[ArrayDataSource alloc] initWithItems:@[@"a", @"b"]
                                                      cellIdentifier:@"foo"
                                                  configureCellBlock:block];

注意,configureCellBlock 除了存儲(chǔ)對(duì)象以外什么都沒(méi)做,這可以讓我們可以更簡(jiǎn)單地測(cè)試它。

然后,我們?yōu)?table view 創(chuàng)建一個(gè) mock 對(duì)象

id mockTableView = [OCMockObject mockForClass:[UITableView class]];

Data source 將在傳進(jìn)來(lái)的 table view 上調(diào)用 -dequeueReusableCellWithIdentifier:forIndexPath: 方法。我們將告訴 mock object 當(dāng)它收到這個(gè)消息時(shí)要做什么。首先創(chuàng)建一個(gè) cell,然后設(shè)置 mock。

UITableViewCell *cell = [[UITableViewCell alloc] init];
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[[[mockTableView expect] andReturn:cell]
        dequeueReusableCellWithIdentifier:@"foo"
                             forIndexPath:indexPath];

第一次看到它可能會(huì)覺(jué)得有點(diǎn)迷惑。我們?cè)谶@里所做的,是讓 mock 記錄特定的調(diào)用。Mock 不是一個(gè)真正的 table view;我們只是假裝它是。-expect 方法允許我們?cè)O(shè)置一個(gè) mock,讓它知道當(dāng)這個(gè)方法調(diào)用時(shí)要做什么。

另外,-expect 方法也告訴 mock 這個(gè)調(diào)用必須發(fā)生。當(dāng)我們稍后在 mock 上調(diào)用 -verify 時(shí),如果那個(gè)方法沒(méi)有被調(diào)用過(guò),測(cè)試就會(huì)失敗。相應(yīng)地,-stub 方法也用來(lái)設(shè)置 mock 對(duì)象,但它不關(guān)心方法是否被調(diào)用過(guò)。

現(xiàn)在,我們要觸發(fā)代碼運(yùn)行。我們就調(diào)用我們希望測(cè)試的方法。

NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
id result = [dataSource tableView:mockTableView
            cellForRowAtIndexPath:indexPath];

然后我們測(cè)試是否一切正常:

STAssertEquals(result, cell, @"Should return the dummy cell.");
STAssertEquals(configuredCell, cell, @"This should have been passed to the block.");
STAssertEqualObjects(configuredObject, @"a", @"This should have been passed to the block.");
[mockTableView verify];

STAssert 宏測(cè)試值的相等性。注意,前兩個(gè)測(cè)試,我們通過(guò)比較指針來(lái)完成;我們不使用 -isEqual:,是因?yàn)槲覀儗?shí)際希望測(cè)試的是 result,cellconfiguredCell 都是同一個(gè)對(duì)象。第三個(gè)測(cè)試要用 -isEqual:,最后我們調(diào)用 mock 的 -verify 方法。

注意,在示例程序中,我們是這樣設(shè)置 mock 的:

id mockTableView = [self autoVerifiedMockForClass:[UITableView class]];

這是我們測(cè)試基類(lèi)中的一個(gè)方便的封裝,它會(huì)在測(cè)試最后自動(dòng)調(diào)用 -verify 方法。

測(cè)試 UITableViewController

下面,我們轉(zhuǎn)向 PhotosViewController。它是個(gè) UITableViewController 的子類(lèi),它使用了我們剛才測(cè)試過(guò)的 data source。View controller 剩下的代碼已經(jīng)相當(dāng)簡(jiǎn)單了。

我們想測(cè)試點(diǎn)擊 cell 后把我們帶到詳情頁(yè)面,即一個(gè) PhotoViewController 的實(shí)例被 push 到 navigation controller 里面。我們?cè)俅问褂?mocking 來(lái)讓測(cè)試盡可能不依賴(lài)于其他部分。

首先我們創(chuàng)建一個(gè) UINavigationController 的 mock:

id mockNavController = [OCMockObject mockForClass:[UINavigationController class]];

接下來(lái),我們要使用部分 mocking。我們希望 PhotosViewController 實(shí)例的 navigationController 返回 mockNavController。我們不能直接設(shè)置 navigation controller,所以我們簡(jiǎn)單地用 stub 來(lái)替換掉 PhotosViewController 實(shí)例這個(gè)方法,讓它返回 mockNavController 就可以了。

PhotosViewController *photosViewController = [[PhotosViewController alloc] init];
id photosViewControllerMock = [OCMockObject partialMockForObject:photosViewController];
[[[photosViewControllerMock stub] andReturn:mockNavController] navigationController];

現(xiàn)在,任何時(shí)候?qū)?photosViewController 調(diào)用 -navigationController 方法,都會(huì)返回 mockNavController。這是個(gè)強(qiáng)大的技巧,OCMock 就有這樣的本領(lǐng)。

接下來(lái),我們要告訴 navigation controller mock 我們調(diào)用的期望,即,一個(gè) photo 不為 nil 的 detail view controller。

UIViewController* viewController = [OCMArg checkWithBlock:^BOOL(id obj) {
    PhotoViewController *vc = obj;
    return ([vc isKindOfClass:[PhotoViewController class]] &&
            (vc.photo != nil));
}];
[[mockNavController expect] pushViewController:viewController animated:YES];

現(xiàn)在,我們觸發(fā) view 加載,并且模擬一行被點(diǎn)擊:

UIView *view = photosViewController.view;
STAssertNotNil(view, @"");
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
[photosViewController tableView:photosViewController.tableView
        didSelectRowAtIndexPath:indexPath];

最后我們驗(yàn)證 mocks 上期望的方法被調(diào)用過(guò):

[mockNavController verify];
[photosViewControllerMock verify];

現(xiàn)在我們有了一個(gè)測(cè)試,用來(lái)測(cè)試和 navigation controller 的交互,以及正確 view controller 的創(chuàng)建。

又一次地,我們?cè)谑纠绦蛑惺褂昧吮憬莸姆椒ǎ?/p>

- (id)autoVerifiedMockForClass:(Class)aClass;
- (id)autoVerifiedPartialMockForObject:(id)object;

于是,我們不需要記住調(diào)用 -verify。

進(jìn)一步探索

就像你從上面看到的那樣,部分 mocking 非常強(qiáng)大。如果你看看 -[PhotosViewController setupTableView] 方法的源碼,你就會(huì)看到它是如何從 app delegate 中取出 model 對(duì)象的。

NSArray *photos = [AppDelegate sharedDelegate].store.sortedPhotos;

上面的測(cè)試依賴(lài)于這行代碼。打破這種依賴(lài)的一種方式是再次使用 部分 mocking,讓 app delegate 返回預(yù)定義的數(shù)據(jù),就像這樣:

id storeMock; // 假設(shè)我們已經(jīng)設(shè)置過(guò)了
id appDelegate = [AppDelegate sharedDelegate]
id appDelegateMock = [OCMockObject partialMockForObject:appDelegate];
[[[appDelegateMock stub] andReturn:storeMock] store];

現(xiàn)在,無(wú)論何時(shí)調(diào)用 [AppDelegate sharedDelegate].store ,它將返回 storeMock。將這個(gè)技術(shù)使用好的話(huà),可以確保讓你的測(cè)試恰到好處地在保持簡(jiǎn)單和應(yīng)對(duì)復(fù)雜之間找到平衡。

需要記住的事

部分 mock 技術(shù)將會(huì)在 mocks 的存在期間替換并保持被 mocking 的對(duì)象,并且一直有效。你可以通過(guò)提前調(diào)用 [aMock stopMocking] 來(lái)終于這種行為。大多數(shù)時(shí)候,你希望 部分 mock 在整個(gè)測(cè)試期間都保持有效。如果要提前終止,請(qǐng)確保在測(cè)試方法最后放置 [aMock verify]。否則 ARC 會(huì)過(guò)早釋放這個(gè) mock,這樣你就不能 -verify 了,這不太可能是你想要的結(jié)果。

測(cè)試 NIB 加載

PhotoCell 設(shè)置在一個(gè) NIB 中,我們可以寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試來(lái)檢查 outlets 設(shè)置得是否正確。我們來(lái)回顧一下 PhotoCell 類(lèi):

@interface PhotoCell : UITableViewCell

+ (UINib *)nib;

@property (weak, nonatomic) IBOutlet UILabel* photoTitleLabel;
@property (weak, nonatomic) IBOutlet UILabel* photoDateLabel;

@end

我們的簡(jiǎn)單測(cè)試的實(shí)現(xiàn)看上去是這樣:

@implementation PhotoCellTests

- (void)testNibLoading;
{
    UINib *nib = [PhotoCell nib];
    STAssertNotNil(nib, @"");

    NSArray *a = [nib instantiateWithOwner:nil options:@{}];
    STAssertEquals([a count], (NSUInteger) 1, @"");
    PhotoCell *cell = a[0];
    STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @"");

    // 檢查 outlet 是否正確設(shè)置
    STAssertNotNil(cell.photoTitleLabel, @"");
    STAssertNotNil(cell.photoDateLabel, @"");
}

@end

非?;A(chǔ),但是能出色完成工作。

值得一提的是,當(dāng)有發(fā)生改變時(shí),我們需要同時(shí)更新測(cè)試以及相應(yīng)的類(lèi)或 nib 。這是事實(shí)。你需要考慮改變類(lèi)或者 nib 文件時(shí)可能會(huì)打破原有的 outlets 連接。如果你用了 .xib 文件,你可能要注意了,這是經(jīng)常發(fā)生的事。

關(guān)于 Class 和 Injection

我們已經(jīng)從與 Xcode 集成得知,測(cè)試 bundle 會(huì)注入到應(yīng)用程序中。省略注入的如何工作的細(xì)節(jié)(它本身是個(gè)巨大的話(huà)題),簡(jiǎn)單地說(shuō):注入是把待注入的 bundle(我們的測(cè)試 bundle)中的 Objective-C 類(lèi)添加到運(yùn)行的應(yīng)用程序中。這很好,因?yàn)檫@樣允許我們運(yùn)行測(cè)試了。

還有一件事會(huì)很讓人迷惑,那就是如果我們同時(shí)把一個(gè)類(lèi)添加到應(yīng)用程序和測(cè)試 bundle中。如果在上面的示例程序中,我們(不小心)把 PhotoCell 類(lèi)同時(shí)添加到測(cè)試 bundle 和應(yīng)用程序里的話(huà),在測(cè)試 bundle 中調(diào)用 [PhotoCell class] 會(huì)返回一個(gè)不同的指針(你應(yīng)用程序中的那個(gè)類(lèi))。于是我們的測(cè)試將會(huì)失?。?/p>

STAssertTrue([cell isMemberOfClass:[PhotoCell class]], @"");

再一次聲明:注入很復(fù)雜。你應(yīng)該確認(rèn)的是:不要把應(yīng)用程序中的 .m 文件添加到測(cè)試 target 中。否則你會(huì)得到預(yù)想不到的行為。

額外的思考

如果你使用一個(gè)持續(xù)集成 (CI) 的解決方案,讓你的測(cè)試啟動(dòng)和運(yùn)行是一個(gè)好主意。詳細(xì)的描述超過(guò)了本文的范圍。這些腳本通過(guò) RunUnitTests 腳本觸發(fā)。還有個(gè) TEST_AFTER_BUILD 環(huán)境變量。

另一種有趣的選擇是創(chuàng)建單獨(dú)的測(cè)試 bundle 來(lái)自動(dòng)化性能測(cè)試。你可以在測(cè)試方法里做任何你想做的。定時(shí)調(diào)用一些方法并使用 STAssert 來(lái)檢查它們是否在特定閾值里面是其中一種選擇。

擴(kuò)展閱讀

上一篇:測(cè)試并發(fā)程序下一篇:iOS 7