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

鍍金池/ 教程/ iOS/ UI 測(cè)試
與四軸無(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)用
線程安全類(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ò)程

UI 測(cè)試

如何進(jìn)行 UI 測(cè)試是 iOS 開(kāi)發(fā)中很常見(jiàn)的問(wèn)題 (我猜測(cè) Mac 等其他 UI 驅(qū)動(dòng)的平臺(tái)也是這樣)。很多人完全不做 UI 測(cè)試,問(wèn)起來(lái)他們經(jīng)常這樣說(shuō):“你只應(yīng)該測(cè)試你的業(yè)務(wù)邏輯?!?也有一部分人想做 UI 測(cè)試,但是覺(jué)得它太復(fù)雜于是便放棄了。

每當(dāng)有人和我說(shuō) UI 測(cè)試很難的時(shí)候,我就會(huì)回想起在一次測(cè)試小組討論中,Landon Fuller 談到 Paper (by 53) 項(xiàng)目的 UI 測(cè)試時(shí)說(shuō)的一段話:

你在屏幕上看到的是各種數(shù)據(jù)和變化綜合之后按照時(shí)間變化所得到的結(jié)果。如果你可以將這些東西分解成可供測(cè)試的單元的話,就意味著你可以將相對(duì)復(fù)雜的內(nèi)容拆解成更容易理解的元素。

Paper 的 UI 相對(duì)來(lái)說(shuō)算是復(fù)雜的了,當(dāng)構(gòu)建這樣的 UI 的時(shí)候,可測(cè)試性一般不會(huì)被考慮在內(nèi)。但是,用戶(hù)的任何一個(gè)行為在代碼中都是被建模處理的,在測(cè)試中模仿用戶(hù)的行為是一件很容易的事情。而問(wèn)題在于大多數(shù)框架,包括 UIKit,都沒(méi)有公開(kāi)的暴露測(cè)試所需要的底層結(jié)構(gòu)。

知道 “測(cè)試什么” 和知道 “如何測(cè)試” 同等重要。我一直都在提及 “UI 測(cè)試”,因?yàn)檫@是一個(gè)被廣為接受的概念,我即將深入討論這類(lèi)測(cè)試。實(shí)際上,我覺(jué)得你可以把 UI 測(cè)試分成兩類(lèi):1) 行為 和 2) 外觀.

我們無(wú)法確定地說(shuō)某種 UI 的外觀是正確的,因?yàn)?UI 的外觀總是在頻繁的變化著。你肯定不想每次修改 UI 的時(shí)候都去修改 UI 的測(cè)試。但這并不意味著你無(wú)法測(cè)試外觀。我對(duì)于這個(gè)方面沒(méi)有任何經(jīng)驗(yàn),但是我們可以用截屏的方式檢驗(yàn)外觀。如果想進(jìn)一步的了解,可以閱讀 Orta 關(guān)于這方面的文章。

在開(kāi)始之前友情提示各位,這篇文章將會(huì)探討用戶(hù)行為測(cè)試相關(guān)的內(nèi)容。我在Github上提供了一個(gè)項(xiàng)目,里面包含了一些實(shí)際的例子,雖然是使用Objective-C編寫(xiě)的iOS項(xiàng)目,但是背后的原理是可以應(yīng)用于Mac和其他UI框架的。

在我測(cè)試用戶(hù)行為時(shí)的第一條原則是:使用代碼的形式來(lái)模擬事件觸發(fā),并讓它們就好像真的是由用戶(hù)的行所觸發(fā)的那樣。這可能會(huì)有點(diǎn)困難,因?yàn)檎缜懊嫠f(shuō),并不是所有的框架都會(huì)公開(kāi)底層接口。

類(lèi)似于 KIFFrankCalabash 的項(xiàng)目解決了這個(gè)問(wèn)題,但是代價(jià)就是需要插入一個(gè)層額外的復(fù)雜度,而我們應(yīng)當(dāng)始終使用最簡(jiǎn)單的可行方案。一般來(lái)說(shuō)都測(cè)試的結(jié)果應(yīng)該是確定的,不修改的話要么就持續(xù)地失敗,要么就持續(xù)地成功。最糟糕的測(cè)試套件就是那些會(huì)隨機(jī)失敗的測(cè)試。我不會(huì)選擇去用那樣的方案,因?yàn)閺奈业慕?jīng)驗(yàn)來(lái)看,它們犧牲了可靠性和穩(wěn)定性而讓項(xiàng)目變得錯(cuò)綜復(fù)雜。

注意到在示例項(xiàng)目中我使用了 SpectaExpecta,嚴(yán)格來(lái)講這并不是最簡(jiǎn)單的解決方案,最簡(jiǎn)單的解決方案是 XCText。但是又有很多原因讓我不得不提及它們。并且從我自己的開(kāi)發(fā)經(jīng)驗(yàn)來(lái)看,它們并不會(huì)影響測(cè)試的可靠性和穩(wěn)定性。事實(shí)上,我敢打賭它們讓我的測(cè)試更好 (這是個(gè)安全的賭局,因?yàn)?strong>好是個(gè)模糊的概念的^_^)。

不管測(cè)試方法是什么,當(dāng)測(cè)試用戶(hù)行為的時(shí)候,我們總是想盡可能接近于用戶(hù)的真實(shí)操作。當(dāng)用戶(hù)與應(yīng)用交互的時(shí)候,我們往往希望能夠用代碼重現(xiàn)出來(lái)。想象一下,當(dāng)用戶(hù)看著一個(gè) ViewController,然后點(diǎn)擊了一個(gè)按鈕,彈出了一個(gè)新的 ViewController。你應(yīng)該是希望測(cè)試可以展示原始的 ViewConnector,并且實(shí)現(xiàn)點(diǎn)擊按鈕操作,然后確保呈現(xiàn)一個(gè)新的 ViewController。

專(zhuān)注于用代碼來(lái)模擬用戶(hù)交互,你可以一次驗(yàn)證多件事情。最重要的,你可以驗(yàn)證核實(shí)期望的行為。作為附贈(zèng),你也同時(shí)測(cè)試了控件正確被初始化以及它們的 action 是被正確設(shè)置的。

舉個(gè)例子,比如在某個(gè)測(cè)試中,我們直接調(diào)用了一個(gè)行為方法。這并不需要把你的測(cè)試和按鈕要做的事情連接起來(lái),當(dāng)然實(shí)際上這樣的測(cè)試也不會(huì)去做這件事。但是如果按鈕的 target 或者 action 改變了,你的測(cè)試依舊可以通過(guò)。你希望證實(shí)的其實(shí)是按鈕在按照你的計(jì)劃行事。而至于按鈕調(diào)用什么方法,針對(duì)什么對(duì)象,這都不是在測(cè)試中該考慮的內(nèi)容。

UIKit 在 UIControl 里提供了非常有用的 sendActionsForControlEvents: 方法,我們可以用來(lái)模仿用戶(hù)操作。比如,用它來(lái)點(diǎn)擊按鈕:

[_button sendActionsForControlEvent: UIControlEventTouchUpInside];

類(lèi)似地,調(diào)用這個(gè)函數(shù)來(lái)切換 UISegmentedControl 的選項(xiàng)卡:

segments.selectedSegmentIndex = 1;
[segments sendActionsForControlEvent: UIControlEventValueChanged];

注意在這里并不只是發(fā)送了 UIControlValueChanged 這個(gè)消息。當(dāng)一個(gè)用戶(hù)和控件交互的時(shí)候,它會(huì)先改變選中的 index 值,然后再發(fā)送 UIControlValueChanged 消息。這是一個(gè)非常好的例子,示范了如何通過(guò)代碼模擬用戶(hù)行為。

UIKit 中并不是所有的控件都有一個(gè)等價(jià)于 sendActionsForControlEvents: 的方法。但是只要有創(chuàng)造力的話,總是能找到變通的方法的。正如前面所說(shuō),最重要的是使用代碼去模擬用戶(hù)觸發(fā)了這個(gè)事件。

舉個(gè)例子,UITableView 并沒(méi)有函數(shù)用來(lái)選中單元格并且讓它去調(diào)用對(duì)應(yīng)的一系列委托方法。在示例項(xiàng)目中通過(guò)兩種方式實(shí)現(xiàn)了這個(gè)功能。

第一種方法是針對(duì) storyboard 的:它通過(guò)手動(dòng)觸發(fā)你希望的單元格來(lái)調(diào)用對(duì)應(yīng)的 segue。不幸的是,這并不能驗(yàn)證單元格都是和 segue 關(guān)聯(lián)的:

[_tableViewController performSegueWithIdentifier:@"TableViewPushSegue" sender:nil];

另一個(gè)選擇則不需要 storyboard 的參與,在測(cè)試代碼里手動(dòng)調(diào)用 tableView:didSelectRowAtIndexPath: 這個(gè)委托方法。如果你使用 storyboard,你可以依舊使用segue,但是你需要從委托方法中手動(dòng)調(diào)用:

[_viewController.tableView.delegate tableView:_viewController.tableView didSelectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
expect(_viewController.navigationController.topViewController).to.beKindOf([PresentedViewController class]);

我更傾向于第二種選擇,它完全將測(cè)試從 ViewController 的呈現(xiàn)方式中解耦。它可以是一個(gè)自定義的 segue,或者 presentViewController:animated:completion,或者是其他的甚至 Apple 還沒(méi)發(fā)明的方式。不過(guò),所有測(cè)試所關(guān)心的是最后的 topViewController 屬性是不是像預(yù)期的一樣。最好的選擇是讓 TableView 自己去選中一行數(shù)據(jù)并且觸發(fā)對(duì)應(yīng)的響應(yīng) action,不過(guò)現(xiàn)在這個(gè)方法行不通。

作為測(cè)試控件的最后一個(gè)示例,我想展示一下 UIBarButtonItem 的特殊情況。它們沒(méi)有 sendActionsForControlEvent: 方法,因?yàn)樗鼈儧](méi)有繼承自 UIControl 類(lèi)。讓我們看看對(duì)于這樣的情況,如何發(fā)送按鈕事件,以及,對(duì)于我們的代碼而言,如何讓它看起來(lái)像是被用戶(hù)點(diǎn)擊了。

UIBarButtonItem 并不像 UIControlUIBarButtonItem 只擁有一個(gè) target 和一個(gè) action 與它關(guān)聯(lián)。調(diào)用這個(gè)事件很簡(jiǎn)單:

[_viewController.barButton.target  performSelector:_viewController.barButton.action
                                         withObject:_viewController.barButton];

如果你在使用 ARC 那么編譯器會(huì)抱怨說(shuō)無(wú)法從未知的 selector 中推斷出內(nèi)存管理的方式。這種狀況對(duì)我而言是不可接受的,因?yàn)樵谖已劾锞婢褪清e(cuò)誤。

一個(gè)選擇是用 #pragma directive 來(lái)隱藏警告,另一個(gè)選擇就是使用直接使用runtime:

#import <objc/message.h>

objc_msgSend(_viewController.barButton.target, _viewController.barButton.action, _viewController.barButton);

我更喜歡 runtime 的方式,因?yàn)槲也幌矚g我的代碼被 pragma directives 搞得一團(tuán)糟。而且也因?yàn)樗o了我一個(gè)實(shí)際使用 runtime 的借口。

說(shuō)句實(shí)話,我并不百分百的確定這些 "解決方案" 不會(huì)出問(wèn)題,因?yàn)檫@并沒(méi)有解決根本的警告。測(cè)試的生命周期往往是短暫的,所以任何在測(cè)試操作中發(fā)生的內(nèi)存缺陷都不足以引起內(nèi)存問(wèn)題。雖然在我使用的這段時(shí)間一直沒(méi)什么問(wèn)題,但是其實(shí)我對(duì)這種情況也不是十分清楚,而且它有可能會(huì)隨機(jī)的在某個(gè)問(wèn)題報(bào)出異常。如果有任何建議,歡迎在這里提出來(lái)。

在文章的最后,我想再說(shuō)一說(shuō) ViewController。ViewController 可能是 iOS 應(yīng)用中最重要的部分,它被抽象出來(lái)調(diào)節(jié)視圖和業(yè)務(wù)邏輯的關(guān)系。為了能更好的測(cè)試用戶(hù)行為,我們不得不呈現(xiàn) ViewController。但是,在測(cè)試用例中呈現(xiàn) ViewController 讓我很快得出結(jié)論:在構(gòu)建它們的過(guò)程中,適合測(cè)試并不在考慮之內(nèi)。

Presenting and dismissing view controllers is the best way to make sure every test has a consistent start state. Unfortunately, doing so in rapid succession—like a test runner does—will quickly result in error messages like:

顯示和隱藏 ViewController 是確保每個(gè)測(cè)試都有一個(gè)不變的初始狀態(tài)的最好方式。但是不幸的是,在連續(xù)快速的這樣做之后 -- 測(cè)試?yán)锟隙ǘ歼@么做 -- 很快就會(huì)導(dǎo)致下面的錯(cuò)誤信息:

  • Warning: Attempt to dismiss from view controller <UINavigationController: 0x109518bd0> while a presentation or dismiss is in progress!
  • Warning: Attempt to present <PresentedViewController: 0x10940ba30> on <UINavigationController: 0x109518bd0> while a presentation is in progress!
  • Unbalanced calls to begin/end appearance transitions for <UINavigationController: 0x109518bd0>
  • nested push animation can result in corrupted navigation bar

一套測(cè)試應(yīng)該盡可能的快,一直等到每一個(gè) ViewController 的展示結(jié)束是不可接受的。最終我們發(fā)現(xiàn),這些警告都是基于單窗口的。只要在獨(dú)立的窗口展示每一個(gè) ViewController,就可以給你的測(cè)試一個(gè)始終一致的開(kāi)始狀態(tài),也保證它運(yùn)行起來(lái)足夠的快。通過(guò)在每個(gè)獨(dú)立的窗口展示分別,你就可以不需要等到展示或者消失過(guò)程結(jié)束了。

對(duì)于 ViewController 還有一些問(wèn)題。比如,push 到導(dǎo)航控制器的操作發(fā)生在下一個(gè) run loop,而使用 modal 的方式彈出窗口卻不是這樣。如果你想嘗試一下這種測(cè)試方式,我建議你看一下我的 ViewController 測(cè)試助手,它會(huì)幫你解決這些問(wèn)題。

當(dāng)測(cè)試行為的時(shí)候,你經(jīng)常需要證實(shí),在某個(gè)交互之后,一個(gè)新的 ViewController 可以正常的彈出來(lái)。換句話說(shuō),你需要證實(shí)當(dāng)前 ViewController 結(jié)構(gòu)的狀態(tài)。UIKit 在這個(gè)方面做的很好,它提供了一系列必要的方法,幫助你完成這個(gè)工作。比如下面這個(gè)例子,它可以讓你確定 ViewController 有沒(méi)有正確地以 modal 形式彈出:

expect(_viewController.presentedViewController).to.beKindOf([PresentedViewController class]);

或者以 push 進(jìn)導(dǎo)航控制器:

expect(_viewController.navigationController.topViewController).to.beKindOf([PresentedViewController class]);

UI測(cè)試其實(shí)并不難,只需要清楚你需要測(cè)試的內(nèi)容就行。你需要測(cè)試的是用戶(hù)交互,而不是應(yīng)用的外觀。通過(guò)創(chuàng)造力和不斷的堅(jiān)持,大多數(shù)框架的缺點(diǎn)都是可以通過(guò)變通的方法解決的,而不用犧牲測(cè)試套件的穩(wěn)定性和可維護(hù)性。時(shí)刻記著,讓你的測(cè)試盡可能接近用戶(hù)的真實(shí)操作。