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

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

置換測試: Mock, Stub 和其他

簡介

在理想情況下,你所做的所有測試都是能應(yīng)對你實(shí)際代碼的高級測試。例如,UI 測試將模擬實(shí)際的用戶輸入(Klaas 在他的文章中有討論)等等。實(shí)但際上,這并非永遠(yuǎn)都是個好主意。為每個測試用例都訪問一次數(shù)據(jù)庫或者旋轉(zhuǎn)一次 UI 會使你的測試跑得非常慢,這會降低你的生產(chǎn)力,并導(dǎo)致你不去經(jīng)常跑那些測試。若你測試的某段代碼依賴于網(wǎng)絡(luò)連接,這會要求你的測試環(huán)境具備網(wǎng)絡(luò)接入條件,而且這也難以模擬某些特殊的測試,比如當(dāng)電話處于飛行模式情況下的時候。

正因如此,我們可以用一些模擬代碼替換你的實(shí)際代碼來編寫一些測試用例。

什么時候你會需要用到一些模擬 (mock) 對象呢?

讓我們從以下這些不同類型的模擬對象的基本定義開始。

double 可以理解為置換,它是所有模擬測試對象的統(tǒng)稱,我們也可以稱它為替身。一般來說,當(dāng)你創(chuàng)建任意一種測試置換對象時,它將被用來替代某個指定類的對象。

stub 可以理解為測試樁,它能實(shí)現(xiàn)當(dāng)特定的方法被調(diào)用時,返回一個指定的模擬值。如果你的測試用例需要一個伴生對象來提供一些數(shù)據(jù),可以使用 stub 來取代數(shù)據(jù)源,在測試設(shè)置時可以指定返回每次一致的模擬數(shù)據(jù)。

spy 可以理解為偵查,它負(fù)責(zé)匯報情況,持續(xù)追蹤什么方法被調(diào)用了,以及調(diào)用過程中傳遞了哪些參數(shù)。你能用它來實(shí)現(xiàn)測試斷言,比如一個特定的方法是否被調(diào)用或者是否使用正確的參數(shù)調(diào)用。當(dāng)你需要測試兩個對象間的某些協(xié)議或者關(guān)系時會非常有用。

mock 與 spy 類似,但在使用上有些許不同。spy 追蹤所有的方法調(diào)用,并在事后讓你寫斷言,而 mock 通常需要你事先設(shè)定期望。你告訴它你期望發(fā)生什么,然后執(zhí)行測試代碼并驗(yàn)證最后的結(jié)果與事先定義的期望是否一致。

fake 是一個具備完整功能實(shí)現(xiàn)和行為的對象,行為上來說它和這個類型的真實(shí)對象上一樣,但不同于它所模擬的類,它使測試變得更加容易。一個典型的例子是使用內(nèi)存中的數(shù)據(jù)庫來生成一個數(shù)據(jù)持久化對象,而不是去訪問一個真正的生產(chǎn)環(huán)境的數(shù)據(jù)庫。

實(shí)踐中,這些術(shù)語常常用起來不同于它們的定義,甚至可以互換。稍后我們在這篇文章中會看到一些庫,它們自認(rèn)為自己是 "mock 對象框架",但是其實(shí)它們也提供 stub 的功能,而且驗(yàn)證行為的方式也類似于我描述的 "spy" 而不是 "mock"。所以不要太過于陷入這些詞匯的細(xì)節(jié);我下這些定義更多的是因?yàn)橐诟邔哟紊蠀^(qū)分這些概念,并且它對考慮不同類型測試對象的行為會有幫助。

如果你對不同類型的模擬測試對象更多的細(xì)節(jié)討論感興趣,Martin Fowler 的文章 "Mocks Aren't Stubs" 被認(rèn)為是關(guān)于這個問題的權(quán)威討論。

模擬主義者 (Mockists) vs. 統(tǒng)計(jì)主義者 (Statists)

許多關(guān)于模擬對象的討論主要是衍生自 Fowler 的文章的,它們討論了兩種不同類型的程序員,模擬主義者和統(tǒng)計(jì)主義者,所寫的測試。

模擬主義的方式是測試對象之間的交互。通過使用模擬對象,你可以更容易地驗(yàn)證被測對象是否遵循了它與其他類已建立的協(xié)議,使得在正確的時間發(fā)生正確的外部調(diào)用。對于那些使用行為驅(qū)動 (behavior-driven) 的開發(fā)者來說,這種測試可以驅(qū)動出更好的生產(chǎn)代碼,因?yàn)槟阈枰鞔_模擬出特定的方法,這可以幫你設(shè)計(jì)出在兩個對象之間使用的更優(yōu)雅的API,這種想法與模擬驅(qū)動緊密聯(lián)系在一起。因此模擬主義的測試更偏向于單元級別的測試,而不是完全的端到端 (end-to-end) 測試。

統(tǒng)計(jì)主義的方式是不使用模擬對象。這種思路是測試時只測試狀態(tài)而不是行為,因此這種類型的測試更加健壯。使用模擬測試時,如果你更新了實(shí)際類的行為,模擬類也需要同步更新;如果你忘了這么做,你可能會遇到測試可以通過但是代碼卻不能正確工作的情況。通過強(qiáng)調(diào)在測試環(huán)境中只使用那些真正的代碼,統(tǒng)計(jì)主義的測試可以幫助你減少測試代碼和實(shí)現(xiàn)代碼的耦合度,并降低出錯率。這種類型的測試,您可能已經(jīng)猜到,適合于更全面的端到端的測試。

當(dāng)然,并不是說有兩個對立的程序員學(xué)派;你不可能看到模擬主義和統(tǒng)計(jì)主義的當(dāng)街對決。這種分歧是有用的,但是,得認(rèn)識到 mock 在有些時候是你的工具箱里最好的工具,但是有時候又不是。不同類型的測試適用于不同的任務(wù),并且最高效的測試套件往往是不同測試風(fēng)格的集合體。仔細(xì)考慮你到底想要用單個測試來驗(yàn)證些什么,這能幫助你找到最合適的測試方式,而且能幫你決定對于當(dāng)前工作來說,使用模擬測試對象是否是正確的工具。

深入代碼

理論上談起來所有一切都沒什么問題,但讓我們來看一個你需要用到 mock 的真實(shí)用例。

讓我們試著測試一個對象,它上面有一個方法,是通過調(diào)用 UIApplicationopenURL: 方法來打開另外一個應(yīng)用程序。(這是我在測試我的 IntentKit 庫時遇到的一個真實(shí)問題。) 給這個用例寫一個端到端的測試,就算是有可能做到,也是非常困難的,因?yàn)?'成功狀態(tài)' 本身導(dǎo)致了應(yīng)用程序的關(guān)閉。自然的選擇是,模擬出一個 UIApplication 對象,并驗(yàn)證這個模擬對象是否確實(shí)調(diào)用了 openURL 方法打開正確的 URL。

假設(shè)這個對象有這樣的方法:

@interface AppLinker : NSObject
        - (instancetype)initWithApplication:(UIApplication *)application;
        - (void)doSomething:(NSURL *)url;
@end

這是一個非常牽強(qiáng)的例子,但是請容忍我一下。在這個例子中,你會注意到我們使用了構(gòu)造方法進(jìn)行注入,當(dāng)我們創(chuàng)建 AppLinker 的對象時將 UIApplication 對象注入到其中。大部分情況下,使用模擬對象要求使用某種形式的依賴注入。如果這個概念對你很陌生,請一定看看本期的 Jon 的文章 中的描述。

OCMockito

OCMockito 是一個非常輕量級的使用模擬對象的庫:

UIApplication *app = mock([UIApplication class]);
AppLinker *linker = [AppLinker alloc] initWithApplication:app];
NSURL *url = [NSURL urlWithString:@"https://google.com"];

[linker doSomething:URL];

[verify(app) openURL:url];

OCMock

OCMock 是另一個 Objective-C 的模擬對象庫。和 OCMockito 類似,它提供了關(guān)于 stub 和 mock 的所有功能,并且包括了你可能需要的一切功能。它比 OCMockito 的功能更強(qiáng),依賴于你的個人選擇,各有利弊。

在最基本層面上,我們可以使用 OCMock 來重寫出與之前非常類似的測試:

id app = OCMClassMock([UIApplication class]);
AppLinker *linker = [AppLinker alloc] initWithApplication:app];
NSURL *url = [NSURL urlWithString:@"https://google.com"];

[linker doSomething:url];

OCMVerify([app openURL:url]);

這種在你測試后再驗(yàn)證調(diào)用方法的模擬測試風(fēng)格被認(rèn)為是一種 “運(yùn)行后驗(yàn)證” 的方式。OCMock 只在最近 3.0 版本后增加了對該功能的支持。同時它也支持老版本的風(fēng)格,即對期望運(yùn)行的驗(yàn)證,在執(zhí)行測試代碼前先設(shè)定對測試結(jié)果的期望。最后,你只需要驗(yàn)證期望和實(shí)際結(jié)果是否對應(yīng):

id app = OCMClassMock([UIApplication class]);

AppLinker *linker = [AppLinker alloc] initWithApplication:app];
NSURL *url = [NSURL urlWithString:@"https://google.com"];

OCMExpect([app openURL:url]);

[linker doSomething:url];

OCMVerifyAll();

由于 OCMock 也支持對類方法的 stub,你也可以用這種方式來測試,如果 doSomething 方法通過 [UIApplication sharedApplication] 來實(shí)現(xiàn)而不是 UIApplication 對象的注入初始化:

id app = OCMClassMock([UIApplication class]);
OCMStub([app sharedInstance]).andReturn(app);

AppLinker *linker = [AppLinker alloc] init];
NSURL *url = [NSURL urlWithString:@"https://google.com"];

[linker doSomething:url];

OCMVerify([app openURL:url]);

你會發(fā)現(xiàn) stub 類方法和 stub 實(shí)例方法看起來是一樣的。

構(gòu)建你自己的測試

對于像這種簡單的用例,你也許不需要這么重量級的模擬對象測試庫。通常,你只需要創(chuàng)建你自己的模擬對象來測試你關(guān)心的行為:

@interface FakeApplication : NSObject
    @property (readwrite, nonatomic, strong) NSURL *lastOpenedURL;

    - (void)openURL:(NSURL *)url;
@end

@implementation FakeApplication
    - (void)openURL:(NSURL *)url {
        self.lastOpenedURL = url;
    }
@end

以下是測試:

FakeApplication *app = [[FakeApplication alloc] init];
AppLinker *linker = [AppLinker alloc] initWithApplication:app];
NSURL *url = [NSURL urlWithString:@"https://google.com"];

[linker doSomething:url];

XCAssertEqual(app.lastOpenedURL, url, @"Did not open the expected URL");

對于類似這個已經(jīng)設(shè)計(jì)好的例子,就可能會出現(xiàn)這種情況,創(chuàng)造你自己的模擬對象只是增加了很多不必要的樣板,但如果你覺得需要模擬更為復(fù)雜的對象交互,那么完全控制模擬對象的行為就會非常有價值。

使用哪一個?

選擇哪一種方案完全依賴于你的具體測試情況以及你的個人偏好。OCMockito 和 OCMock 都可以通過 CocoaPods 安裝,將它們集成到你現(xiàn)有的測試環(huán)境都非常簡單,但需要注意的是,除非你需要,否則避免新增一些其他的依賴。另外除非真的需要,最好就都創(chuàng)建一些簡單的模擬對象。

模擬測試時的注意事項(xiàng)

在任何形式的測試中你有可能碰到的最大的問題之一是寫的測試和實(shí)現(xiàn)代碼耦合過于緊密。測試中一個最重要的關(guān)鍵點(diǎn)是降低未來的變化所帶來的成本;如果改變代碼的實(shí)現(xiàn)細(xì)節(jié)破壞了當(dāng)前的測試,則這種成本已經(jīng)增加了。也就是說,其實(shí)為了最小化由于使用模擬測試所造成不利影響,其實(shí)你有很多可以做的。

依賴注入是你的好伙伴

如果你還沒有使用依賴注入,或許你會需要它。雖然有時候不使用依賴注入來模擬對象也是可以的的 (比如以上面使用 OCMock 模擬類方法),但是通常是不太可能的。即使可能,設(shè)置測試所引入的復(fù)雜度也可能大于它能帶來的好處。如果你使用依賴注入的話,你會發(fā)現(xiàn)使用 stub 和 mock 方式寫測試要容易的多。

不要模擬你沒有的

許多有經(jīng)驗(yàn)的測試人員都會警告你“不要模擬你沒有的東西”,意思是你應(yīng)該只為你代碼庫本身擁有的對象創(chuàng)建 mock 或 stub,而不是為第三方依賴或一些庫去創(chuàng)建。這里主要有兩個原因,一個是基于實(shí)際情況的,一個是更具有哲學(xué)性的考慮。

對于你的代碼庫,你對它不同接口的穩(wěn)定性和不穩(wěn)定性大概會有一個感覺,所以你可以通過你的直覺來判斷使用替換測試的方法是不是可能會導(dǎo)致測試過于脆弱。一般來說,你對第三方代碼沒有這樣的把握。為了解決這個問題,一個通用的做法是為第三方代碼創(chuàng)建包裝類來抽象出它的行為。在某些情況下,僅僅是轉(zhuǎn)移復(fù)雜性而不是降低復(fù)雜性往往是沒什么意義的。但是在一些情況下,你會很經(jīng)常使用你的第三方代碼,這時這就是一個精簡你測試的好方法。你的單元測試能模擬出自定義對象,并使用高層次的集成或功能測試來測試你的包裝類本身。

iOS 和 OS X 開發(fā)世界的唯一性導(dǎo)致了事情稍微復(fù)雜一些。我們做的很多事情都依賴于 Apple 的框架,這個框架遠(yuǎn)遠(yuǎn)超過了其他語言的一些標(biāo)準(zhǔn)庫。雖然 NSUserDefaults 不是一個“你擁有”對象,但是,如果你發(fā)現(xiàn)你有需要把它模擬出來,那就放心去做吧,蘋果不太可能會在未來的 Xcode 的版本中推出打破這個 API 的變化。

另一個不要模擬第三方依賴庫的原因更具哲學(xué)性。使用模擬主義風(fēng)格書寫測試的部分原因是通過這樣的測試能比較容易的找到兩個對象間最清晰可行的接口。但是如果是第三方依賴,你無法對其進(jìn)行控制;API 協(xié)議中的一些詳細(xì)信息已經(jīng)被第三方庫定死了,所以你無法通過測試來通過實(shí)驗(yàn)有效地驗(yàn)證接口是否有改進(jìn)的余地。這本身不是問題,但在很多情況下,它降低了模擬測試的效果,直到把模擬測試的優(yōu)點(diǎn)抹殺殆盡。

不要模仿我!

測試沒有銀彈;基于你的個人傾向和代碼的具體特性,不同的情況下需要使用不同的策略。測試替身可能不適用所有的情況,但它們會是你測試工具箱中一個非常有效的工具。不管你傾向于使用框架在單元測試中模擬出一切,還是只是根據(jù)需要創(chuàng)建你自己的模擬對象,當(dāng)你思考如何測試你的代碼時,牢記模擬對象是非常有意義。