版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2019.04.19 星期五 |
前言
自動化Test可以通過編寫代碼滞诺、或者是記錄開發(fā)者的操作過程并代碼化定拟,來實現(xiàn)自動化測試等功能尼啡。接下來幾篇我們就說一下該技術(shù)的使用琐凭。感興趣的可以看下面幾篇芽隆。
1. 自動化Test使用詳細解析(一) —— 基本使用(一)
開始
首先看下寫作環(huán)境
Swift 4.2, iOS 12, Xcode 10
本篇主要了解如何將單元測試和UI測試添加到iOS應用程序,以及如何檢查代碼覆蓋率统屈。
寫作測試不是很迷人胚吁,但是由于測試可以防止讓你的閃亮應用程序變成了一個充滿bug的垃圾,這是必要的愁憔。 如果您正在閱讀本教程腕扶,您已經(jīng)知道您應該為您的代碼和UI編寫測試,但您可能不知道如何去做吨掌。
您可能有一個有效的應用程序半抱,但您想測試您為擴展應用程序所做的更改脓恕。 也許您已經(jīng)編寫了測試,但不確定它們是否是正確的測試代虾。 或者进肯,您已經(jīng)開始研究新的應用程序,并希望隨時進行測試棉磨。
本教程將向您展示:
- 如何使用Xcode的Test導航器測試應用程序的模型和異步方法
- 如何使用
stubs and mocks
與庫或系統(tǒng)對象的交互 - 如何測試UI和性能
- 如何使用代碼覆蓋工具
在此過程中江掩,您將獲得測試ninjas
所使用的一些詞匯。
Figuring Out What to Test
在編寫任何測試之前乘瓤,了解基礎(chǔ)知識非常重要环形。 你需要測試什么?
如果您的目標是擴展現(xiàn)有應用程序衙傀,則應首先為計劃更改的任何組件編寫測試抬吟。
通常,測試應包括:
- 核心功能:模型類和方法及其與控制器的交互
- 最常見的UI工作流程
- 邊界條件
- Bug修復
1. Best Practices for Testing
首字母縮略詞FIRST
描述了有效單元測試的一套簡明標準统抬。 這些標準是:
- Fast - 快速:測試應該快速進行火本。
- Independent/Isolated - 獨立/隔離:測試不應彼此共享狀態(tài)。
- Repeatable - 可重復:每次運行測試時都應獲得相同的結(jié)果聪建。 外部數(shù)據(jù)提供者或并發(fā)問題可能導致間歇性故障钙畔。
- Self-validating - 自我驗證:測試應完全自動化。 輸出應該是“通過”或“失敗”金麸,而不是依賴于程序員對日志文件的解釋擎析。
- Timely - 及時:理想情況下,應在編寫測試的生產(chǎn)代碼(測試驅(qū)動開發(fā))之前編寫測試挥下。
遵循FIRST
原則將使您的測試保持清晰且有用揍魂,而不是為您的應用程序設(shè)置障礙。
打開已經(jīng)下載的工程文件:
-
BullsEye
基于iOS Apprentice
中的示例應用程序棚瘟。 游戲邏輯位于BullsEyeGame
類中现斋,您將在本教程中測試它。 -
HalfTunes
是URLSession
教程中示例應用程序的更新版本偎蘸。 用戶可以在iTunes API
中查詢歌曲庄蹋,然后下載和播放歌曲片段。
Unit Testing in Xcode
Test navigator
提供了最簡單的測試方法禀苦,您將使用它來創(chuàng)建測試目標并針對您的應用程序運行測試蔓肯。
1. Creating a Unit Test Target
打開BullsEye
項目并按Command-6
打開Test navigator
遂鹊。
單擊左下角的+按鈕振乏,然后從菜單中選擇New Unit Test Target ...
:
接受默認名稱BullsEyeTests
。 當test bundle
出現(xiàn)在Test navigator
中時秉扑,單擊以在編輯器中打開該包慧邮。 如果bundle
未自動顯示调限,請單擊其他導航器之一進行故障排除,然后返回Test navigator
误澳。
默認模板導入測試框架XCTest
耻矮,并使用setUp()
,tearDown()
和示例測試方法定義XCTestCase
的BullsEyeTests
子類忆谓。
運行測試有三種方法:
- 1)
Product ? Test or Command-U
裆装。 這兩個都運行所有測試類。 - 2) 單擊
Test navigator
中的箭頭按鈕倡缠。 - 3) 單擊
gutter
中的菱形按鈕哨免。
您還可以通過在Test navigator
或gutter
中單擊其菱形來運行單個測試方法。
嘗試不同的方式來運行測試昙沦,以了解它需要多長時間以及它看起來像什么琢唾。 樣本測試還沒有做任何事情,所以它們運行得非扯芤快采桃!
當所有測試成功后,菱形將變?yōu)榫G色并顯示復選標記丘损。 您可以單擊testPerformanceExample()
末尾的灰色菱形以打開性能結(jié)果:
本教程不需要testPerformanceExample()
或testExample()
普办,因此請刪除它們。
2. Using XCTAssert to Test Models
首先号俐,您將使用XCTAssert
函數(shù)來測試BullsEye
模型的核心功能:BullsEyeGame
對象是否正確計算了一輪的分數(shù)泌豆?
在BullsEyeTests.swift
中,在import
語句下方添加以下行:
@testable import BullsEye
這使得單元測試可以訪問BullsEye
中的internal
類型和功能吏饿。
在BullsEyeTests
類的頂部踪危,添加以下屬性:
var sut: BullsEyeGame!
這為BullsEyeGame
創(chuàng)建了一個占位符,它是System Under Test (SUT)
猪落,或者是測試用例類與測試有關(guān)的對象贞远。
接下來,用這個替換setup()
的內(nèi)容:
super.setUp()
sut = BullsEyeGame()
sut.startNewGame()
這會在類級別創(chuàng)建BullsEyeGame
對象笨忌,因此此測試類中的所有測試都可以訪問SUT
對象的屬性和方法蓝仲。
在這里,您還可以調(diào)用游戲的startNewGame()
來初始化targetValue
官疲。 許多測試將使用targetValue
來測試游戲是否正確計算得分袱结。
在您忘記之前,請在tearDown()
中釋放您的SUT
對象途凫。 將其內(nèi)容替換為:
sut = nil
super.tearDown()
注意:最好在
setUp()
中創(chuàng)建SUT
并在tearDown()
中釋放它垢夹,以確保每個測試都以一個干凈的平板開始。 有關(guān)更多討論维费,請查看Jon Reid’s post關(guān)于此主題的帖子果元。
Writing Your First Test
現(xiàn)在促王,您已經(jīng)準備好編寫第一個測試了!
將以下代碼添加到BullsEyeTests
的末尾:
func testScoreIsComputed() {
// 1. given
let guess = sut.targetValue + 5
// 2. when
sut.check(guess: guess)
// 3. then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
測試方法的名稱始終以test
開頭而晒,然后是對其測試內(nèi)容的描述蝇狼。
將測試格式化為given, when and then
的部分是一種很好的做法:
-
Given:在這里,您可以設(shè)置所需的任何值倡怎。 在此示例中迅耘,您將創(chuàng)建一個
guess
值,以便指定它與targetValue
的差異监署。 -
When:在本節(jié)中豹障,您將執(zhí)行正在測試的代碼:調(diào)用
check(guess :)
。 -
Then:這是您將斷言您期望的結(jié)果的部分焦匈,如果測試失敗則打印一條消息血公。 在這種情況下,
sut.scoreRound
應該等于95(100 - 5)
缓熟。
單擊gutter
或Test navigator
中的菱形圖標運行測試累魔。 這將構(gòu)建并運行應用程序,菱形圖標將變?yōu)榫G色復選標記够滑!
注意:要查看
XCTestAssertions
的完整列表垦写,請轉(zhuǎn)到 Apple’s Assertions Listed by Category。
1. Debugging a Test
BullsEyeGame
故意內(nèi)置了一個錯誤彰触,你現(xiàn)在就可以練習找到它梯投。 要查看操作中的bug,您將創(chuàng)建一個測試况毅,從given
部分中的targetValue
中減去5
分蓖,并使其他所有內(nèi)容保持不變。
添加以下測試:
func testScoreIsComputedWhenGuessLTTarget() {
// 1. given
let guess = sut.targetValue - 5
// 2. when
sut.check(guess: guess)
// 3. then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
guess
和targetValue
之間的差異仍為5
尔许,因此得分仍應為95
么鹤。
在“斷點”導航器Breakpoint navigator
中,添加Test Failure Breakpoint
味廊。 當測試方法發(fā)布失敗assertion
時蒸甜,這將停止測試運行。
運行測試余佛,它應該在測試失敗時停在XCTAssertEqual
行柠新。
在調(diào)試控制臺中檢查sut
和guess
:
guess
是targetValue - 5
但是scoreRound
是105
,而不是95
辉巡!
要進一步調(diào)查恨憎,請使用正常的調(diào)試過程:在when
語句中設(shè)置一個斷點,在BullsEyeGame.swift
中設(shè)置一個斷點红氯,在check(guess :)
中框咙,它會產(chǎn)生difference
。 然后再次運行測試痢甘,并跳過let difference
語句以檢查應用程序中difference
的值:
問題是difference
是負值喇嘱,所以得分是100 - ( - 5)
。 要解決此問題塞栅,您應該使用difference
的絕對值者铜。 在check(guess :)
中,取消注釋正確的行并刪除不正確的行放椰。
刪除兩個斷點作烟,然后再次運行測試以確認它現(xiàn)在成功。
2. Using XCTestExpectation to Test Asynchronous Operations
現(xiàn)在您已經(jīng)學會了如何測試模型和調(diào)試測試失敗砾医,現(xiàn)在是時候繼續(xù)測試異步代碼了拿撩。
打開HalfTunes
項目。 它使用URLSession
來查詢iTunes API
并下載歌曲樣本如蚜。 假設(shè)您要修改它以使用AlamoFire
進行網(wǎng)絡(luò)操作压恒。 要查看是否有任何中斷,您應該為網(wǎng)絡(luò)操作編寫測試并在更改代碼之前和之后運行它們错邦。
URLSession
方法是異步的:它們立即返回探赫,但直到稍后才完成運行。 要測試異步方法撬呢,請使用XCTestExpectation
使測試等待異步操作完成伦吠。
異步測試通常很慢,因此您應該將它們與更快的單元測試分開魂拦。
創(chuàng)建一個名為HalfTunesSlowTests
的新單元測試目標毛仪。 打開HalfTunesSlowTests
類,并在現(xiàn)有import
語句下方導入HalfTunes
應用程序模塊:
@testable import HalfTunes
此類中的所有測試都使用默認的URLSession
將請求發(fā)送到Apple
的服務器芯勘,因此聲明一個sut
對象潭千,在setUp()
中創(chuàng)建它并在tearDown()
中釋放它。
用以下內(nèi)容替換HalfTunesSlowTests
類的內(nèi)容:
var sut: URLSession!
override func setUp() {
super.setUp()
sut = URLSession(configuration: .default)
}
override func tearDown() {
sut = nil
super.tearDown()
}
下面借尿,添加異步測試:
// Asynchronous test: success fast, failure slow
func testValidCallToiTunesGetsHTTPStatusCode200() {
// given
let url =
URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")
// 1
let promise = expectation(description: "Status code: 200")
// when
let dataTask = sut.dataTask(with: url!) { data, response, error in
// then
if let error = error {
XCTFail("Error: \(error.localizedDescription)")
return
} else if let statusCode = (response as? HTTPURLResponse)?.statusCode {
if statusCode == 200 {
// 2
promise.fulfill()
} else {
XCTFail("Status code: \(statusCode)")
}
}
}
dataTask.resume()
// 3
wait(for: [promise], timeout: 5)
}
此測試檢查向iTunes
發(fā)送有效查詢是否返回200
狀態(tài)代碼刨晴。 大多數(shù)代碼與您在應用程序中編寫的代碼相同,使用以下附加行:
- 1)
expectation(description :)
:返回存儲在promise
中的XCTestExpectation
對象路翻。description
參數(shù)描述了您期望發(fā)生的事情狈癞。 - 2)
promise.fulfill()
:在異步方法的完成處理程序的成功條件閉包中調(diào)用它來標記已滿足期望。 - 3)
wait(for:timeout :)
:保持測試運行茂契,直到滿足所有期望蝶桶,或超時間隔timeout
結(jié)束,以先發(fā)生者為準掉冶。
運行測試真竖。 如果您已連接到互聯(lián)網(wǎng)脐雪,則應用程序在模擬器中加載后,測試應該需要大約一秒鐘才能成功恢共。
3. Failing Fast
失敗會傷害战秋,但它不需要永遠。
要體驗失敗讨韭,只需從URL
中的“itunes”
中刪除's'
即可:
let url =
URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")
運行測試脂信。 它失敗了,但需要完整的超時間隔透硝! 這是因為你假設(shè)請求總是成功狰闪,那就是你調(diào)用promise.fulfill()
的地方。 由于請求失敗濒生,僅在超時到期時才完成埋泵。
您可以通過更改以下假設(shè)來改進此問題并使測試失敗:不要等待請求成功罪治,而是等待直到調(diào)用異步方法的完成處理程序秋泄。 一旦應用程序收到來自服務器的響應(OK或錯誤),就會發(fā)生這種情況规阀,這符合預期恒序。 然后,您的測試可以檢查請求是否成功谁撼。
要了解其工作原理歧胁,請創(chuàng)建一個新測試。
但首先厉碟,通過撤消對url
所做的更改來修復上一個測試喊巍。
然后,將以下測試添加到您的類:
func testCallToiTunesCompletes() {
// given
let url =
URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")
let promise = expectation(description: "Completion handler invoked")
var statusCode: Int?
var responseError: Error?
// when
let dataTask = sut.dataTask(with: url!) { data, response, error in
statusCode = (response as? HTTPURLResponse)?.statusCode
responseError = error
promise.fulfill()
}
dataTask.resume()
wait(for: [promise], timeout: 5)
// then
XCTAssertNil(responseError)
XCTAssertEqual(statusCode, 200)
}
關(guān)鍵的區(qū)別在于箍鼓,簡單地輸入完成處理程序就能滿足期望崭参,而這只需要大約一秒鐘的時間。 如果請求失敗款咖,則then
斷言失敗何暮。
運行測試。 它現(xiàn)在應該花費大約一秒鐘才能失敗铐殃。 它失敗是因為請求失敗海洼,而不是因為測試運行超過了timeout
。
修復url
富腊,然后再次運行測試以確認它現(xiàn)在成功坏逢。
后記
本篇主要介紹了單元測試和UI Test使用簡單示例,感興趣的給個贊或者關(guān)注~~~