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

鍍金池/ 教程/ iOS/ 糟糕的測(cè)試
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類型
深入理解 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 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶端
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ǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(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)追蹤
依賴注入
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í)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

糟糕的測(cè)試

時(shí)至今日,我寫(xiě)自動(dòng)化測(cè)試也已經(jīng)有些年頭了,我不得不承認(rèn)當(dāng)它能使代碼更容易維護(hù),因此我依然對(duì)這項(xiàng)技術(shù)著迷。本文中,我希望分享一些我的經(jīng)驗(yàn),以及我從他人或自己的一次次嘗試中所吸取的教訓(xùn)。

這些年,我聽(tīng)到了許多關(guān)于寫(xiě)自動(dòng)化測(cè)試的好的 (以及不好的) 理由。從積極的方面來(lái)說(shuō),寫(xiě)自動(dòng)化測(cè)試能夠:

  • 使重構(gòu)更簡(jiǎn)單
  • 避免代碼惡化
  • 提供了可執(zhí)行的說(shuō)明和文檔
  • 減少了創(chuàng)建軟件的時(shí)間
  • 降低了創(chuàng)建軟件的代價(jià)

的確,你可以說(shuō)這些都是對(duì)的,但是我想提出一個(gè)關(guān)于這些理由的另一個(gè)視角 —— 一個(gè)統(tǒng)一的視角

自動(dòng)化測(cè)試唯一的理由是它讓我們能在將來(lái)修改我們的代碼。

換句話說(shuō):

一個(gè)測(cè)試能夠體現(xiàn)回報(bào)價(jià)值的時(shí)候僅僅是當(dāng)我們想修改我們的代碼的時(shí)候。

讓我們看一看這個(gè)經(jīng)典論斷是如何支持我們前面提到的理由的:

  • 使重構(gòu)更簡(jiǎn)單 —— 你可以自信的修改實(shí)現(xiàn)細(xì)節(jié),而不用去觸及公有 API。
  • 避免代碼惡化—— 惡化在什么時(shí)候發(fā)生?在你修改代碼的時(shí)候。
  • 提供了可執(zhí)行的說(shuō)明和文檔 —— 你在什么時(shí)候更想知道軟件實(shí)際上是如何工作的?在你想修改它們的時(shí)候
  • 減少了創(chuàng)建軟件的時(shí)間 —— 怎么減少時(shí)間的?是通過(guò)更快速地修改你的代碼,出錯(cuò)時(shí)測(cè)試會(huì)自信地告訴你哪里出錯(cuò)了
  • 降低了創(chuàng)建軟件的代價(jià) —— 好吧,時(shí)間就是金錢,我的朋友

是的,上面所有的理由在某些方面是對(duì)的,但是這些理由適用于我們開(kāi)發(fā)者的就是自動(dòng)化測(cè)試能夠讓我們修改代碼。

注意,我在這里不會(huì)寫(xiě)關(guān)于測(cè)試的設(shè)計(jì)所能得到的反饋,比如 TDD。那可以成為一個(gè)單獨(dú)的話題。我們將要談?wù)摰臏y(cè)試是已經(jīng)寫(xiě)好的測(cè)試。

看起來(lái)好像寫(xiě)測(cè)試和如何寫(xiě)測(cè)試應(yīng)該以修改作為動(dòng)機(jī)。

一個(gè)簡(jiǎn)單的考慮這個(gè)問(wèn)題的方法是在寫(xiě)測(cè)試的時(shí)候,向你的測(cè)試提出下面兩個(gè)問(wèn)題:

“如果我修改了我的生產(chǎn)代碼,測(cè)試是會(huì)失敗 (還是通過(guò)) 呢?”

“那是一個(gè)讓測(cè)試失敗 (或者通過(guò)) 的好的理由么?”

如果你發(fā)現(xiàn)那是一個(gè)讓測(cè)試失敗 (或者通過(guò)) 的不好的理由,那么請(qǐng)修正它。

那樣,將來(lái)你修改你的代碼的時(shí)候,你的測(cè)試只會(huì)因?yàn)楹玫睦碛啥ㄟ^(guò)或者失敗,這會(huì)比因?yàn)椴缓玫睦碛啥〉墓殴值臏y(cè)試得到的回報(bào)要好。

現(xiàn)在,你可能仍然會(huì)問(wèn):“什么才是最重要的?”

讓我們用另一個(gè)問(wèn)題來(lái)回答這個(gè)問(wèn)題:當(dāng)我們修改代碼的時(shí)候,測(cè)試為什么會(huì)出錯(cuò)?

我們都認(rèn)同的一個(gè)觀點(diǎn)是我們進(jìn)行測(cè)試的主要原因是為了能夠輕松地修改代碼。如果是那樣的話,那些失敗的測(cè)試是如何幫助我們的?那些失敗的測(cè)試除了是噪音之外什么也不是 —— 它們甚至?xí)璧K著我們完成工作。那么,怎樣做測(cè)試才能幫助我們呢?

這取決于我們修改代碼的理由。

修改代碼的行為

首先,起點(diǎn)必須是測(cè)試全部是綠色的,也就是說(shuō)所有的測(cè)試都已經(jīng)通過(guò)。

如果你想通過(guò)修改代碼來(lái)修改它們的行為 (也就是,修改代碼做的事情),你需要:

  1. 找到定義當(dāng)你想要修改的前行為的測(cè)試。
  2. 修改這些測(cè)試來(lái)滿足新的期望的行為。
  3. 運(yùn)行測(cè)試,查看那些被修改的測(cè)試是否失敗了。
  4. 更新你的代碼,使得所有的測(cè)試重新通過(guò)。

在這一過(guò)程的結(jié)尾,我們又回到了起點(diǎn)——所有的測(cè)試都通過(guò)了,如果需要,我們已經(jīng)準(zhǔn)備好了再次開(kāi)始。

因?yàn)槟阒滥男y(cè)試失敗了以及哪些代碼的修改使得它們又通過(guò)了,你會(huì)很有信心,因?yàn)槟阒恍薷牧四阆胍薷牡牟糠帧_@就是自動(dòng)化測(cè)試如何幫助我們通過(guò)修改代碼來(lái)修改代碼的行為的。

注意,看到一個(gè)測(cè)試失敗是正常的,因?yàn)樗俏覀冋诟碌男袨橄鄬?duì)應(yīng)的測(cè)試。

重構(gòu):修改代碼的實(shí)現(xiàn) —— 保持行為不變

同樣,起點(diǎn)應(yīng)該是測(cè)試全是綠色的。

如果希望修改一段代碼的實(shí)現(xiàn)讓它變得更簡(jiǎn)單,高效,易于擴(kuò)展等等 (也就是說(shuō),修改怎么做,而不是做什么),應(yīng)該遵循接下來(lái)的原則:

在不觸及測(cè)試的前提下修改你的代碼。

當(dāng)修改后的代碼已經(jīng)簡(jiǎn)單、快速、更靈活時(shí),你的測(cè)試應(yīng)該仍然是綠色的。在重構(gòu)的時(shí)候,測(cè)試應(yīng)該只在代碼出錯(cuò)的時(shí)候失敗,例如修改了代碼的外部行為。當(dāng)發(fā)生這種情況時(shí),你應(yīng)該退回到那個(gè)錯(cuò)誤然后回到綠色的狀態(tài)

因?yàn)槟愕臏y(cè)試總是在綠色的狀態(tài),你知道你沒(méi)有破壞任何事情。這就是自動(dòng)化測(cè)試如何讓我們修改我們的代碼的方式。

在這種情況下,看到測(cè)試失敗是不應(yīng)該的。因?yàn)檫@意味著:

  • 我們無(wú)意識(shí)地修改了代碼的外部行為。慶幸的是,我們的測(cè)試幫助了我們發(fā)現(xiàn)這些錯(cuò)誤。
  • 我們沒(méi)有修改代碼的外部行為。太不幸了,這才是最大的麻煩。

我希望測(cè)試在上面的情形下能夠幫助我們。所以讓我們來(lái)看一些具體的能讓我們的測(cè)試更有效的 tips。

優(yōu)秀實(shí)踐入門(mén)

在討論如何寫(xiě)測(cè)試之前,我想迅速地回顧一些優(yōu)秀實(shí)踐。有 5 條被認(rèn)為是每個(gè)測(cè)試都應(yīng)該遵守的基本原則。便于記憶這 5 條規(guī)則的縮寫(xiě)是: F.I.R.S.T.

測(cè)試應(yīng)該:

  • 很快速(Fast) —— 測(cè)試應(yīng)該能夠被經(jīng)常執(zhí)行。
  • 能隔離(Isolated) —— 測(cè)試本身不能依賴于外部因素或者其他測(cè)試的結(jié)果。
  • 可重復(fù)(Repeatable) —— 每次運(yùn)行測(cè)試都應(yīng)該產(chǎn)生相同的結(jié)果。
  • 帶自檢(Self-verifying) —— 測(cè)試應(yīng)該包括斷言,不需要人為干預(yù)。
  • 夠及時(shí)(Timely) —— 測(cè)試應(yīng)該和生產(chǎn)代碼一同書(shū)寫(xiě)。

更多關(guān)于這些規(guī)則的內(nèi)容,你可以閱讀 Tim Ottinger 和 Jeff Langr 的這篇文章。

壞的實(shí)踐

如何將測(cè)試的結(jié)果收益最大化?一言以蔽之:

不要將測(cè)試和實(shí)現(xiàn)細(xì)節(jié)耦合在一起

不要測(cè)試私有方法

說(shuō)得夠多了。

私有方法意味著私有。如果你感到有必要測(cè)試一個(gè)私有方法,那么那個(gè)私有方法一定含有概念性錯(cuò)誤,通常是作為私有方法,它做的太多了, 從而違背了單一職責(zé)原則

今天:假設(shè)你的類有一個(gè)私有方法。它做了太多的事情,所以你決定測(cè)試它。你僅僅為了測(cè)試,就讓那個(gè)方法變成公有的。它本來(lái)只被同一個(gè)類的其他的公有方法在內(nèi)部使用。然后你為這個(gè)私有 (從技術(shù)上來(lái)說(shuō)現(xiàn)在公有了的) 方法編寫(xiě)測(cè)試。

明天:因?yàn)樾枨笊系囊恍┳兓?(這完全是有可能的),你決定修改這個(gè)方法。你發(fā)現(xiàn)一些同事在其他的類中使用了這個(gè)方法,因?yàn)樗麄冋f(shuō) “這個(gè)方法做了我想要的事情”。畢竟,它是公有的,不是么?這個(gè)私有方法不是公有 API 的一部分。你要想修改這個(gè)方法就不得不破壞你同事的代碼。

應(yīng)該做什么:將私有方法抽離到一個(gè)單獨(dú)的類中,給這個(gè)類一個(gè)定義良好的約定,然后單獨(dú)地測(cè)試它。當(dāng)其他的測(cè)試代碼依賴這個(gè)新的類的時(shí)候,如果有必要的話,你可以進(jìn)行置換測(cè)試。

那么,我們?nèi)绾螠y(cè)試一個(gè)類的私有方法呢?通過(guò)這個(gè)類的公有 API。永遠(yuǎn)通過(guò)公有 API 測(cè)試你的代碼。程序的公有 API 定義了一個(gè)約定,它是一組關(guān)于你的程序?qū)?yīng)于不同輸入時(shí)定義良好的一組期望。私有 API (私有方法或者整個(gè)類) 并沒(méi)有定義約定,并且可以不經(jīng)通知自行修改,所以你的測(cè)試 (或者你的同事) 不能依賴于它們。

通過(guò)這種方法測(cè)試你的私有方法,你可以自由地修改你的 (真正的) 私有代碼,并且通過(guò)劃分成只做一件事情,并經(jīng)過(guò)正確測(cè)試的小的類,來(lái)提升代碼的設(shè)計(jì)。

不要 Stub 私有方法

Stub 私有方法和測(cè)試私有方法具有相同的危害,更重要的是,stub 私有方法將會(huì)使程序難以調(diào)試。通常來(lái)說(shuō),用于 stub 的庫(kù)會(huì)依賴于一些不尋常的技巧來(lái)完成工作,這使得發(fā)現(xiàn)一個(gè)測(cè)試為什么會(huì)失敗變的困難。

同樣,當(dāng)我們 stub 一個(gè)方法的時(shí)候,我們必須依據(jù)它做出的約定來(lái)進(jìn)行。但是私有方法沒(méi)有指定的約定的 —— 畢竟,這也是為什么它們是私有的原因。由于私有方法的行為可以不經(jīng)通知自行修改,你的 stub 可能與實(shí)際情況背道而馳,但是你的測(cè)試仍然會(huì)通過(guò)。這是多么的可怕啊,讓我們來(lái)看一個(gè)例子:

今天:一個(gè)類的公有方法依賴于該類的一個(gè)私有方法。這個(gè)私有方法 foo 永遠(yuǎn)不會(huì)返回空。為公有方法編寫(xiě)的測(cè)試為了方便起見(jiàn),我們 stub 出了私有方法。當(dāng) stub foo 方法的時(shí)候,你永遠(yuǎn)不會(huì)考慮到 foo 返回為空的情況,因?yàn)楝F(xiàn)在這種情況永遠(yuǎn)不會(huì)發(fā)生。

明天:這個(gè)私有方法被修改了,現(xiàn)在它返回空了。它是一個(gè)私有方法,所以這沒(méi)什么問(wèn)題。為公有方法編寫(xiě)的測(cè)試不會(huì)相應(yīng)地被修改 (“我正在修改一個(gè)私有方法,所以我為什么要更新我的測(cè)試?”)。公有方法現(xiàn)在在私有方法返回空的情況下會(huì)出錯(cuò),但是測(cè)試仍然會(huì)通過(guò)!

這實(shí)在太可怕了。

應(yīng)該做什么:由于 foo 做的事情太多了,所以應(yīng)該將它抽離至一個(gè)新的類,然后單獨(dú)地測(cè)試它。然后,在測(cè)試的時(shí)候,為那個(gè)新類提供一個(gè)置換。

不要 Stub 外部庫(kù)

第三方代碼不應(yīng)該在你的測(cè)試中直接出現(xiàn)。

今天:你的網(wǎng)絡(luò)部分的代碼依賴于著名的 HTTP 庫(kù) LSNetworking.為了避免使用實(shí)際的網(wǎng)絡(luò) (為了讓你的測(cè)試更快速更可信),你 stub 了那個(gè)庫(kù)中的方法 -[LSNetworking makeGETrequest:],沒(méi)有通過(guò)實(shí)際的網(wǎng)絡(luò)合適地替代了它的行為 (它通過(guò)一個(gè)封裝好的響應(yīng)調(diào)用了執(zhí)行成功的回調(diào))。

明天:你需要使用一個(gè)替代品來(lái)取代 LSNetworking (可能是 LSNetworking 已經(jīng)不再維護(hù)或者是你需要換成一個(gè)更先進(jìn)的庫(kù),因?yàn)樗泻芏嗄阈枰男绿匦缘鹊?。這是一次重構(gòu),所以你不應(yīng)該修改測(cè)試。你替換了庫(kù)。你的測(cè)試會(huì)失敗,因?yàn)橐蕾嚨木W(wǎng)絡(luò)沒(méi)有被 stub (-[LSNetworking makeGETrequest:]不會(huì)被調(diào)用)。

應(yīng)該做什么:測(cè)試中,依靠 stubbing 傘 (umbrella stubbing) 來(lái)替代那個(gè)庫(kù)的全部功能。

stubbing 傘 (一個(gè)我剛剛發(fā)明的術(shù)語(yǔ)) 包括了對(duì)于所有你的代碼可能用到的方式 -- 不管事現(xiàn)在還是將來(lái) -- 的 stub。它們可以通過(guò)良好聲明的 API 完成一些任務(wù),而不去關(guān)心實(shí)現(xiàn)的細(xì)節(jié)。

正如上面的那個(gè)例子,你的代碼今天可能依賴于 "HTTP 庫(kù) A",但是還是有別的可能的方式發(fā)起 HTTP 請(qǐng)求,不是么?比如 "HTTP 庫(kù) B"。

舉個(gè)例子,我的一個(gè)開(kāi)源項(xiàng)目 Nocilla 就為網(wǎng)絡(luò)代碼提供 stubbing 傘的解決方案。通過(guò) Nocilla 你可以不依賴任何 HTTP 庫(kù),以聲明的方式 stub HTTP 請(qǐng)求。Nocilla 可以 stubbing 的任何一個(gè) HTTP 庫(kù),所以你不會(huì)將測(cè)試和實(shí)現(xiàn)細(xì)節(jié)耦合在一起。這使得你能夠在不修改測(cè)試的情況下切換網(wǎng)絡(luò)框架。

另一個(gè)例子是 stub 日期,在大多數(shù)編程語(yǔ)言中都有很多中方法獲取當(dāng)前時(shí)間,但是像 TUDelorean 這樣的庫(kù)可以 stub 每一個(gè)與日期相關(guān)的 API,所以你可以模仿一個(gè)不同的系統(tǒng)日期用來(lái)測(cè)試。這讓你你不用修改測(cè)試就可以重構(gòu)不同的日期 API 的實(shí)現(xiàn)細(xì)節(jié)。

除了 HTTP 和日期,在擁有各種各樣 API 的其他領(lǐng)域,你可以用類似的方式來(lái)實(shí)現(xiàn) stubbing 傘,或者你可以創(chuàng)建你自己的開(kāi)源解決方案并分享到社區(qū),這樣其他人就可以正確地編寫(xiě)測(cè)試了。

正確地 Stub 依賴

這部分和前一點(diǎn)關(guān)系密切,但是這部分的情況更普遍。我們的生產(chǎn)代碼通常依賴于某些事情的完成。比如,一個(gè)依賴能夠幫助我們查詢數(shù)據(jù)庫(kù)。通常這些依賴提供了多種方法來(lái)實(shí)現(xiàn)相同的事情,或者說(shuō)至少是實(shí)現(xiàn)相同的外部行為;在我們的數(shù)據(jù)庫(kù)的例子中,你可以使用 find 方法通過(guò) ID 來(lái)獲取一條記錄,或者使用 where 子句獲取相同的記錄。當(dāng)我們僅僅 stub 可能的機(jī)制中的一個(gè)的時(shí)候,問(wèn)題就出現(xiàn)了。如果我們僅僅 stub 了 find 方法 (我們的生產(chǎn)代碼使用的機(jī)制),但是沒(méi)有 stub 其他的可能性,比如 where 子句,當(dāng)我們決定使用 where 子句取代 find 方法來(lái)重構(gòu)我們的實(shí)現(xiàn)的時(shí)候,我們的測(cè)試就會(huì)失敗,即使代碼的外部行為并沒(méi)有修改。

今天:UsersController 類依賴于 UserRepository 類從數(shù)據(jù)庫(kù)中取得用戶。你正在測(cè)試 UsersController 并且你為了以確定的方式更快地運(yùn)行,你 stub 了 UsersRepositoryfind 方法,這實(shí)在是太棒了。

明天:你決定使用 UsersRepository 的新的可讀性更高的查詢語(yǔ)法來(lái)重構(gòu) UsersController,因?yàn)檫@是一次重構(gòu),所以不應(yīng)該觸及測(cè)試。為了找到感興趣的記錄,你使用了可讀性更高的 where 方法更新了 UsersController?,F(xiàn)在你的測(cè)試會(huì)失敗,因?yàn)闇y(cè)試 stub 了 find 方法,但是沒(méi)有 stub where 方法。

stubbing 傘在某些情況下能幫上忙,但是對(duì)于 UsersController 類的這種情形,沒(méi)有可以替代的庫(kù)能夠從我的數(shù)據(jù)庫(kù)中獲取我的用戶。

應(yīng)該做什么:以測(cè)試為目的,為同一個(gè)類創(chuàng)建可替代的實(shí)現(xiàn),并將它作為置換來(lái)使用。

繼續(xù)我們的例子,我們應(yīng)該提供一個(gè) InMemoryUsersRepository。這個(gè)在內(nèi)存中的替代方案,除了它為提高測(cè)試速度而把數(shù)據(jù)保存在內(nèi)存中之外,它應(yīng)該遵守原始的 UsersRepository 類的每一個(gè)單一方面的約定。這意味著,當(dāng)你重構(gòu) UsersRepository 的時(shí)候,你使用在內(nèi)存中這個(gè)版本做了同樣的事情。為了讓它更清楚:是的,現(xiàn)在你不得不為同一個(gè)類維護(hù)兩套不同的實(shí)現(xiàn)。

現(xiàn)在你可以將這個(gè)輕量級(jí)的版本的依賴作為置換對(duì)象提供給測(cè)試。好的事情是這是一個(gè)完整的實(shí)現(xiàn),所以當(dāng)你決定將實(shí)現(xiàn)從一個(gè)方法移動(dòng)到另一個(gè)方法 (在我們的例子中是從 find 移動(dòng)到 where) 的時(shí)候,正在使用的置換對(duì)象將會(huì)支持新的方法,并且當(dāng)重構(gòu)的時(shí)候,測(cè)試也不會(huì)失敗。

維護(hù)一個(gè)類的另一個(gè)版本沒(méi)有什么問(wèn)題。根據(jù)我的經(jīng)驗(yàn),它最終只會(huì)需要很少的努力,就能得到很大的回報(bào)。

你同樣可以將類的輕量級(jí)版本作為生產(chǎn)代碼的一部分,就像 Core Data 使用棧的內(nèi)存版本一樣。這樣做可能對(duì)某些人有作用。

不要測(cè)試構(gòu)造函數(shù)

構(gòu)造函數(shù)定義的是實(shí)現(xiàn)細(xì)節(jié),你不應(yīng)該測(cè)試構(gòu)造函數(shù),這是因?yàn)槲覀冋J(rèn)同測(cè)試應(yīng)該與實(shí)現(xiàn)細(xì)節(jié)解耦這一觀點(diǎn)。

而且,構(gòu)造函數(shù)不應(yīng)該包含行為,所以沒(méi)有值得測(cè)試的東西。這是因?yàn)槲覀冋J(rèn)同測(cè)試應(yīng)該只對(duì)代碼的行為進(jìn)行這一觀點(diǎn)。

今天:你有一個(gè) Car 類,并包含一個(gè)構(gòu)造函數(shù)。一旦一個(gè) Car 被創(chuàng)建了,你測(cè)試它的 Engine 不為空 (因?yàn)槟阒罉?gòu)造函數(shù)創(chuàng)建了一個(gè)新的 Engine 并將它賦給了變量 _engine)。

明天:Engine 類創(chuàng)建起來(lái)變得代價(jià)很高,所以你決定使用延遲初始化 (lazily initialize),在第一次調(diào)用 Enginegetter 方法時(shí)才初始化 Engine (這是很好的)。 現(xiàn)在為 Car 類的構(gòu)造函數(shù)編寫(xiě)的測(cè)試出問(wèn)題了,即便 Car 類運(yùn)行良好,但 Car 并沒(méi)有包括 Engine。另一個(gè)可能是你的測(cè)試不會(huì)失敗,因?yàn)闇y(cè)試包含 EngineCar 類會(huì)觸發(fā) Engine 的延遲加載。所以我的問(wèn)題是:為什么還要測(cè)試?

應(yīng)該做什么:當(dāng)使用不同的方法創(chuàng)建類的時(shí)候測(cè)試公有 API 的行為。一個(gè)愚蠢的例子:測(cè)試當(dāng) list類被創(chuàng)建并且沒(méi)有包含條目的時(shí)候,list 類的 count 方法的行為。注意,你測(cè)試的是 count 的行為而不是構(gòu)造函數(shù)的行為。

思考一下,類含有多個(gè)構(gòu)造函數(shù)的情形,這可能意味著你的類做了太多事情了。試著將它們拆分成更小的類,但是如果有足夠充分的理由使你的類含有多個(gè)構(gòu)造函數(shù),那么依然遵循同樣的建議。保證你的測(cè)試的是那個(gè)類的公有 API。在這種情況下,使用每一個(gè)構(gòu)造函數(shù)去測(cè)試 (也就是說(shuō),當(dāng)類處在一種初始化狀態(tài)下時(shí),它的行為就是那種狀態(tài)下的;當(dāng)類處在另一種初始化狀態(tài)下時(shí),它的行為是另一種狀態(tài)下的)

結(jié)論

編寫(xiě)測(cè)試是一項(xiàng)投資 —— 我們需要花時(shí)間編寫(xiě)和維護(hù)它們。我們可以證明這種投資有回報(bào)的唯一方法就是我們期望節(jié)省時(shí)間。將實(shí)現(xiàn)細(xì)節(jié)和測(cè)試耦合在一起會(huì)減少測(cè)試帶來(lái)的回報(bào),使得那些投資變得不合算,甚至在某些情況下變得一文不值。

在編寫(xiě)測(cè)試、重構(gòu)以及修改系統(tǒng)行為的時(shí)候,檢查你的測(cè)試在面對(duì)錯(cuò)誤的原因時(shí)是失敗還是通過(guò),然后退一步問(wèn)問(wèn)自己,那些測(cè)試是否能夠最大化你投資的成果。

上一篇:游戲下一篇:項(xiàng)目介紹