版本记录
版本号 | 时间 |
---|---|
V1.0 | 2021.05.20 星期四 |
前言
自动化Test可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动化测试等功能。接下来几篇我们就说一下该技术的使用。感兴趣的可以看下面几篇。
1. 自动化Test使用详细解析(一) —— 基本使用(一)
2. 自动化Test使用详细解析(二) —— 单元测试和UI Test使用简单示例(一)
3. 自动化Test使用详细解析(三) —— 单元测试和UI Test使用简单示例(二)
4. 自动化Test使用详细解析(四) —— 单元测试和UI Test(一)
5. 自动化Test使用详细解析(五) —— 单元测试和UI Test(二)
开始
首先看下主要内容:
了解如何将单元测试和UI测试添加到iOS应用程序,以及如何检查代码覆盖率。内容来自翻译。
接着就看下写作环境:
Swift 5, iOS 14, Xcode 12
下面就是正文啦。
iOS单元测试虽然魅力十足,但是由于测试可以防止您的闪亮应用程序变成bug
缠身的垃圾,因此这很有必要。如果您正在阅读本教程,则已经知道应该为代码和UI编写测试,但是可能不知道如何做。
您可能有一个正在运行的应用程序,但是您想测试为扩展该应用程序所做的更改。也许您已经编写了测试,但是不确定它们是否是正确的测试。或者,您已经开始开发新应用,并希望随时进行测试。
本教程将向您展示如何:
- 使用
Xcode
的Test navigator
来测试应用程序的模型和异步方法 - 使用
stubs and mocks
与库或系统对象的虚假交互 - 测试用户界面
UI
和性能 - 使用代码覆盖率工具
在此过程中,您将掌握测试忍者所使用的一些词汇。
打开入门项目,它包括基于BulletEye
的项目,该项目基于UIKit Apprentice中的示例应用程序。这是一个简单的运气和运气游戏。游戏逻辑位于BullsEyeGame
类中,您将在本教程中对其进行测试。
Figuring out What to Test
在编写任何测试之前,了解基本很重要。您需要测试什么?
如果您的目标是扩展现有应用程序,则应首先为计划更改的任何组件编写测试。
通常,测试应涵盖:
-
Core functionality
:模型类和方法及其与控制器的交互 - 最常见的UI工作流程
- 边界条件
-
Bug
修复
1. Understanding Best Practices for Testing
首字母缩写词FIRST
描述了有效单元测试的一组简明标准。这些标准是:
- Fast - 快速:测试应该快速进行。
- Independent/Isolated - 独立/隔离:测试不应相互共享状态。
- Repeatable - 可重复:每次运行测试时,您都应获得相同的结果。外部数据提供者或并发问题可能会导致间歇性故障。
- Self-validating - 自我验证:测试应完全自动化。输出应该是
“pass” or “fail”
,而不是依赖程序员对日志文件的解释。 - **及时性:理想情况下,您应该在编写要测试的生产代码之前编写测试。这就是所谓的测试驱动开发。
遵循FIRST
原则将使您的测试清晰,有用,而不会成为您应用程序的障碍。
Unit Testing in Xcode
Test navigator
提供了最简单的测试方法。 您将使用它来创建test targets
并针对您的应用运行测试。
1. Creating a Unit Test Target
打开BullsEye
项目,然后按Command-6
打开Test navigator
。
单击左下角的+
,然后从菜单中选择New Unit Test Target…
:
接受默认名称BullsEyeTests
,然后输入com.raywenderlich
作为Organization Identifier
。 当test bundle
出现在Test navigator
中时,通过单击显示三角形将其展开,然后单击BullsEyeTests
以在编辑器中将其打开。
默认模板导入测试框架XCTest
,并使用setUpWithError()
,tearDownWithError()
和示例测试方法定义XCTestCase
的BullsEyeTests
子类。
您可以通过三种方式运行测试:
- 1)
Product ▸ Test or Command-U
。 这两个都运行所有测试类。 - 2) 单击
Test navigator
中的箭头按钮。 - 3) 单击装订线中的菱形按钮。
您也可以通过在Test navigator
或装订线中单击其菱形来运行单个测试方法。
尝试不同的方式运行测试,以了解所需的时间和外观。 样本测试尚无任何功能,因此运行速度非常快!
当所有测试均成功后,菱形将变为绿色并显示选中标记。 单击testPerformanceExample()
末尾的灰色菱形以打开Performance Result
:
您不需要本教程的testPerformanceExample()
或testExample()
,因此请将其删除。
2. Using XCTAssert to Test Models
首先,您将使用XCTAssert
函数测试BullsEye
模型的核心功能:BullsEyeGame
是否正确计算一轮得分?
在BullsEyeTests.swift
中,将此行添加到import XCTest
下面:
@testable import BullsEye
这使单元测试可以访问BullsEye
中的internal
类型和函数。
在BullsEyeTests
的顶部,添加以下属性:
var sut: BullsEyeGame!
这将为BullsEyeGame
创建一个占位符,它是System Under Test (SUT)
或此测试用例类与测试有关的对象。
接下来,用以下内容替换setUpWithError()
的内容:
try super.setUpWithError()
sut = BullsEyeGame()
这样会在类级别创建BullsEyeGame
,因此该测试类中的所有测试都可以访问SUT
对象的属性和方法。
在忘记之前,请在tearDownWithError()
中释放您的SUT
对象。 将其内容替换为:
sut = nil
try super.tearDownWithError()
注意:最好的做法是在
setUpWithError()
中创建SUT
,然后在tearDownWithError()
中释放它,以确保每次测试都从干净的开始。 有关更多讨论,请查看Jon Reid’s post关于该主题的帖子。
Writing Your First Test
现在,您可以开始编写第一个测试了!
将以下代码添加到BullsEyeTests
的末尾,以测试您是否计算了预期得分:
func testScoreIsComputedWhenGuessIsHigherThanTarget() {
// given
let guess = sut.targetValue + 5
// when
sut.check(guess: guess)
// then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
测试方法的名称始终以test
开头,然后是对其进行测试的描述。
最好将测试格式化为given, when and then
的部分:
- 1) Given:在这里,您可以设置所需的任何值。 在此示例中,您将创建一个
guess
值,以便您可以指定它与targetValue
的差异。 - 2) When:在本节中,您将执行要测试的代码:调用
check(guess :)
。 - 3) Then:在此部分中,您将通过测试失败的情况显示一条消息,以确认期望的结果。 在这种情况下,
sut.scoreRound
应该等于95
,因为它是100-5
。
单击装订线或Test navigator
中的菱形图标,运行测试。 这将构建并运行该应用程序,菱形图标将变为绿色的选中标记! 您还将看到Xcode
上方出现一个短暂的弹出窗口,它也表示成功,如下所示:
注意:要查看
XCTestAssertions
的完整列表,请转到Apple’s Assertions Listed by Category。
1. Debugging a Test
BullsEyeGame
特意内置了一个bug
,您将立即练习查找它。 要查看运行中的bug
,您将创建一个测试,该测试将在给定部分的targetValue
中减去5
,并使其他所有内容保持不变。
添加以下测试:
func testScoreIsComputedWhenGuessIsLowerThanTarget() {
// given
let guess = sut.targetValue - 5
// when
sut.check(guess: guess)
// then
XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")
}
guess
和targetValue
之间的差仍然是5
,因此分数仍然应该是95
。
在Breakpoint navigator
中,添加Test Failure Breakpoint
。 当测试方法发布故障断言时,这将停止测试运行。
运行您的测试,它应该在XCTAssertEqual
行处停止,并显示测试失败。
检查调试控制台中的sut and gues
:
guess
是targetValue − 5
,但scoreRound
是105
,而不是95
!
若要进行进一步调查,请使用正常的调试过程:在when
语句中设置一个断点,并在check(guess :)
内部的BullsEyeGame.swift
中设置一个断点,以在此创建difference
。 然后,再次运行测试,并通过let Difference
语句检查应用程序中的difference
值:
问题在于difference
为负,因此分数为100-(-5)
。要解决此问题,您应该使用difference
的绝对值。在check(guess :)
中,取消注释正确的行并删除不正确的行。
删除两个断点,然后再次运行测试以确认现在可以成功进行。
2. Using XCTestExpectation to Test Asynchronous Operations
现在,您已经了解了如何测试模型和调试测试失败,现在该着手测试异步代码了。
BullsEyeGame
使用URLSession
获得一个随机数作为下一个游戏的目标。 URLSession
方法是异步的:它们会立即返回,但要等到稍后才结束运行。要测试异步方法,请使用XCTestExpectation
使测试等待异步操作完成。
异步测试通常很慢,因此应将它们与更快的单元测试分开。
创建一个名为BullsEyeSlowTests
的新单元测试目标。打开全新的测试类BullsEyeSlowTests
,然后在现有import
语句下方导入BullsEye
应用模块:
@testable import BullsEye
此类中的所有测试都使用默认的URLSession
发送请求,因此声明sut
,在setUpWithError()
中创建它,然后在tearDownWithError()
中释放它。 为此,将BullsEyeSlowTests
的内容替换为:
var sut: URLSession!
override func setUpWithError() throws {
try super.setUpWithError()
sut = URLSession(configuration: .default)
}
override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}
接下来,添加此异步测试:
// Asynchronous test: success fast, failure slow
func testValidApiCallGetsHTTPStatusCode200() throws {
// given
let urlString =
"http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
let url = URL(string: urlString)!
// 1
let promise = expectation(description: "Status code: 200")
// when
let dataTask = sut.dataTask(with: url) { _, 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)
}
此测试检查发送有效请求是否返回200
状态码。 大多数代码与您在应用程序中编写的代码相同,但有以下几行:
- 1)
Expectation(description :)
:返回存储在Promise
中的XCTestExpectation
。description
描述了您期望发生的事情。 - 2)
promise.fulfill()
:在异步方法的完成处理程序的成功条件闭包中调用此函数,以标记已达到期望。 - 3)
wait(for:timeout :)
:保持测试运行,直到满足所有期望或timeout
间隔结束(以先发生者为准)。
运行测试。 如果您已连接到互联网,则在将应用程序加载到模拟器中后,测试大约需要一秒钟才能成功。
3. Failing Fast
失败很痛苦,但这并不一定要长久。
要体验失败,只需将testValidApiCallGetsHTTPStatusCode200()
中的URL
更改为无效的URL
:
let url = URL(string: "http://www.randomnumberapi.com/test")!
运行测试。 它失败,但是需要整个超时间隔! 这是因为您假设请求将始终成功,因此您将其称为promise.fulfill()
。 由于请求失败,因此仅在超时到期时才完成。
您可以改善这一点,并通过更改假设使测试更快地失败。 不必等待请求成功,只需等待异步方法的完成处理程序被调用即可。 一旦应用程序从服务器接收到满足预期的响应(“OK”
或“error”
),就会发生这种情况。 然后,您的测试可以检查请求是否成功。
要查看其工作原理,请创建一个新测试。
但首先,通过撤消对url
所做的更改来修复以前的测试。
然后,将以下测试添加到您的类:
func testApiCallCompletes() throws {
// given
let urlString = "http://www.randomnumberapi.com/test"
let url = URL(string: urlString)!
let promise = expectation(description: "Completion handler invoked")
var statusCode: Int?
var responseError: Error?
// when
let dataTask = sut.dataTask(with: url) { _, 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)
}
关键区别在于,只需输入完成处理程序即可满足期望,而这仅需一秒钟即可完成。 如果请求失败,then
断言失败。
运行测试。 现在需要大约一秒钟才能失败。 它失败是因为请求失败,而不是因为测试运行超出了timeout
。
修复url
,然后再次运行测试以确认它现在可以成功。
4. Failing Conditionally
在某些情况下,执行测试没有多大意义。 例如,当testValidApiCallGetsHTTPStatusCode200()
在没有网络连接的情况下运行时会发生什么? 当然,它不应该通过,因为它不会收到200
状态代码。 但是它也不应该失败,因为它没有测试任何东西。
幸运的是,Apple推出了XCTSkip
,以在前提条件失败时跳过测试。 在sut
声明下面添加以下行:
let networkMonitor = NetworkMonitor.shared
NetworkMonitor
包装NWPathMonitor
,从而提供了一种方便的方法来检查网络连接。
在testValidApiCallGetsHTTPStatusCode200()
中,在测试开始时添加XCTSkipUnless
:
try XCTSkipUnless(
networkMonitor.isReachable,
"Network connectivity needed for this test.")
当没有网络可访问时,XCTSkipUnless(_:_ :)
跳过测试。 通过禁用网络连接并运行测试来进行检查。 您会在测试旁边的装订线中看到一个新图标,表明该测试未通过或未通过。
再次启用您的网络连接,然后重新运行测试以确保它在正常情况下仍然成功。将相同的代码添加到testApiCallCompletes()
的开头。
Faking Objects and Interactions
异步测试使您有信心代码可以为异步API
生成正确的输入。您可能还需要测试从URLSession
接收输入时代码是否正常工作,或者是否正确更新UserDefaults
数据库或iCloud
容器。
大多数应用与系统或库对象(您无法控制的对象)进行交互。与这些对象进行交互的测试可能是缓慢且不可重复的,这违反了FIRST
的两个原则。相反,您可以通过从stubs
获取输入或通过更新mock
对象来伪造交互。
当您的代码依赖于系统或库对象时,请进行伪造。为此,可以创建一个假对象来扮演该角色,并将该假对象注入代码中。乔恩·里德(Jon Reid)的Dependency Injection描述了几种方法。
1. Faking Input From Stub
现在,检查应用程序的getRandomNumber(completion :)
是否正确解析了会话下载的数据。您将使用存根数据伪造BullsEyeGame
的会话。
转到Test navigator
,单击+
,然后选择New Unit Test Class…
。将其命名为BullsEyeFakeTests
,将其保存在BullsEyeTests
目录中,然后将目标设置为BullsEyeTests
。
在import
语句下方导入BullsEye
应用模块:
@testable import BullsEye
现在,用以下内容替换BullsEyeFakeTests
的内容:
var sut: BullsEyeGame!
override func setUpWithError() throws {
try super.setUpWithError()
sut = BullsEyeGame()
}
override func tearDownWithError() throws {
sut = nil
try super.tearDownWithError()
}
这将声明SUT
,即BullsEyeGame
,在setUpWithError()
中创建它,并在tearDownWithError()
中释放它。
BullsEye
项目包含支持文件URLSessionStub.swift
。 这定义了一个名为URLSessionProtocol
的简单协议,并带有使用URL
创建数据任务的方法。 它还定义了符合此协议的URLSessionStub
。 它的初始化程序使您可以定义数据任务应返回的数据,响应和错误。
要设置伪造,请转到BullsEyeFakeTests.swift
并添加一个新测试:
func testStartNewRoundUsesRandomValueFromApiRequest() {
// given
// 1
let stubbedData = "[1]".data(using: .utf8)
let urlString =
"http://www.randomnumberapi.com/api/v1.0/random?min=0&max=100&count=1"
let url = URL(string: urlString)!
let stubbedResponse = HTTPURLResponse(
url: url,
statusCode: 200,
httpVersion: nil,
headerFields: nil)
let urlSessionStub = URLSessionStub(
data: stubbedData,
response: stubbedResponse,
error: nil)
sut.urlSession = urlSessionStub
let promise = expectation(description: "Value Received")
// when
sut.startNewRound {
// then
// 2
XCTAssertEqual(self.sut.targetValue, 1)
promise.fulfill()
}
wait(for: [promise], timeout: 5)
}
该测试执行两件事:
- 1) 您设置了伪造的数据和响应,并创建了伪造的会话对象。最后,将假会话作为
sut
的属性注入到应用程序中。 - 2) 您仍然必须将其编写为异步测试,因为
stub
假装是异步方法。检查调用startNewRound(completion :)
是否通过将targetValue
与stub
伪造的数字进行比较来解析伪造的数据。
运行测试。它应该很快就能成功,因为没有任何实际的网络连接!
2. Faking an Update to Mock Object
先前的测试使用stub
提供来自伪造对象的输入。接下来,您将使用mock object
来测试您的代码是否正确更新了UserDefaults
。
这个程序有两种游戏风格。用户可以:
- 1) 移动滑块以匹配目标值。
- 2) 从滑块位置猜测目标值。
右下角的分段控件可切换游戏样式并将其保存在UserDefaults
中。
您的下一个测试将检查应用程序是否正确保存了gameStyle
属性。
向target BullsEyeTests
添加一个新的测试类,并将其命名为BullsEyeMockTests
。在import
语句下面添加以下内容:
@testable import BullsEye
class MockUserDefaults: UserDefaults {
var gameStyleChanged = 0
override func set(_ value: Int, forKey defaultName: String) {
if defaultName == "gameStyle" {
gameStyleChanged += 1
}
}
}
MockUserDefaults
重写set(_:forKey :)
以增加gameStyleChanged
。 相似的测试通常会设置一个Bool变
量,但是递增Int
可以为您提供更大的灵活性。 例如,您的测试可以检查应用程序仅调用一次该方法。
接下来,在BullsEyeMockTests
中声明SUT
和mock
对象:
var sut: ViewController!
var mockUserDefaults: MockUserDefaults!
将setUpWithError()
和tearDownWithError()
替换为:
override func setUpWithError() throws {
try super.setUpWithError()
sut = UIStoryboard(name: "Main", bundle: nil)
.instantiateInitialViewController() as? ViewController
mockUserDefaults = MockUserDefaults(suiteName: "testing")
sut.defaults = mockUserDefaults
}
override func tearDownWithError() throws {
sut = nil
mockUserDefaults = nil
try super.tearDownWithError()
}
这将创建SUT
和mock
对象,并将mock
对象作为SUT
的属性注入。
现在,将模板中的两个默认测试方法替换为:
func testGameStyleCanBeChanged() {
// given
let segmentedControl = UISegmentedControl()
// when
XCTAssertEqual(
mockUserDefaults.gameStyleChanged,
0,
"gameStyleChanged should be 0 before sendActions")
segmentedControl.addTarget(
sut,
action: #selector(ViewController.chooseGameStyle(_:)),
for: .valueChanged)
segmentedControl.sendActions(for: .valueChanged)
// then
XCTAssertEqual(
mockUserDefaults.gameStyleChanged,
1,
"gameStyle user default wasn't changed")
}
when
断言是在测试方法更改分段控件之前gameStyleChanged
标志为0
。 因此,如果then
断言也成立,则意味着set(_:forKey :)
恰好被调用了一次。
运行测试。 它应该成功。
UI Testing in Xcode
UI
测试使您可以测试与用户界面的交互。 用户界面测试的工作原理是通过查询查找应用程序的用户界面对象,综合事件,然后将事件发送到这些对象。 使用该API,您可以检查UI对象的属性和状态,以将其与预期状态进行比较。
在Test navigator
中,添加一个新的UI Test Target
。 检查Target to be Tested
是BullsEye
,然后接受默认名称BullsEyeUITests
。
打开BullsEyeUITests.swift
并将此属性添加到BullsEyeUITests
类的顶部:
var app: XCUIApplication!
删除tearDownWithError()
并将setUpWithError()
的内容替换为以下内容:
try super.setUpWithError()
continueAfterFailure = false
app = XCUIApplication()
app.launch()
删除两个现有的测试,并添加一个名为testGameStyleSwitch()
的新测试。
func testGameStyleSwitch() {
}
在testGameStyleSwitch()
中打开新行,然后单击编辑器窗口底部的红色Record
按钮:
这会以将您的互动记录为测试命令的模式在模拟器中打开该应用。 应用加载后,点击游戏样式开关的Slide
部分和top label
。 再次单击Xcode的Record
按钮以停止记录。
现在,您在testGameStyleSwitch()
中具有以下三行:
let app = XCUIApplication()
app.buttons["Slide"].tap()
app.staticTexts["Get as close as you can to: "].tap()
记录器已创建代码以测试您在应用程序中测试的相同操作。 向游戏样式分段控件和顶部标签发送点击。 您将以此为基础来创建自己的UI测试。 如果您看到其他任何语句,则将其删除。
第一行与您在setUpWithError()
中创建的属性重复,因此请删除该行。 您无需点击任何东西,因此也请删除第2
行和第3
行结尾的.tap()
。现在,打开[“ Slide”]
旁边的小菜单,然后选择segmentedControls.buttons [“ Slide”]
。
您应该得到:
app.segmentedControls.buttons["Slide"]
app.staticTexts["Get as close as you can to: "]
点击其他任何对象,让记录器帮助您找到可以在测试中访问的代码。 现在,用以下代码替换这些行以创建给定的部分:
// given
let slideButton = app.segmentedControls.buttons["Slide"]
let typeButton = app.segmentedControls.buttons["Type"]
let slideLabel = app.staticTexts["Get as close as you can to: "]
let typeLabel = app.staticTexts["Guess where the slider is: "]
现在,您已经有了分段控件中两个按钮的名称以及两个可能的顶部标签,在下面添加以下代码:
// then
if slideButton.isSelected {
XCTAssertTrue(slideLabel.exists)
XCTAssertFalse(typeLabel.exists)
typeButton.tap()
XCTAssertTrue(typeLabel.exists)
XCTAssertFalse(slideLabel.exists)
} else if typeButton.isSelected {
XCTAssertTrue(typeLabel.exists)
XCTAssertFalse(slideLabel.exists)
slideButton.tap()
XCTAssertTrue(slideLabel.exists)
XCTAssertFalse(typeLabel.exists)
}
当您在分段控件中的每个按钮上tap()
时,这将检查是否存在正确的label
。 运行测试 —— 所有断言都应成功。
Testing Performance
根据Apple’s documentation:
A performance test takes a block of code that you want to evaluate and runs it ten times, collecting the average execution time and the standard deviation for the runs. The averaging of these individual measurements form a value for the test run that can then be compared against a baseline to evaluate success or failure.
编写性能测试很简单:只需将要测量的代码放在measure()
的结尾处。 此外,您可以指定多个指标进行衡量。
将以下测试添加到BullsEyeTests
:
func testScoreIsComputedPerformance() {
measure(
metrics: [
XCTClockMetric(),
XCTCPUMetric(),
XCTStorageMetric(),
XCTMemoryMetric()
]
) {
sut.check(guess: 100)
}
}
此测试测量多个指标:
-
XCTClockMetric
度量经过的时间。 -
XCTCPUMetric
跟踪CPU
活动,包括CPU
时间,周期和指令数。 -
XCTStorageMetric
告诉您测试代码将多少数据写入存储。 -
XCTMemoryMetric
跟踪已使用的物理内存量。
运行测试,然后单击measure()
尾随闭包开头旁边显示的图标以查看统计信息。 您可以在Metric
旁边更改所选指标。
单击Set Baseline
以设置参考时间。 再次运行性能测试并查看结果 —— 它可能比基准更好或更差。 使用Edit
按钮可以将基准重置为该新结果。
基准是按设备配置存储的,因此您可以在多个不同的设备上执行相同的测试。 每个都可以维持不同的基准,具体取决于特定配置的处理器速度,内存等。
每当您对应用程序进行更改而可能影响所测试方法的性能时,请再次运行性能测试以查看其与基准的比较情况。
Enabling Code Coverage
代码覆盖率工具会告诉您测试实际在运行哪些应用程序代码,因此您知道该应用程序的哪些部分尚未进行测试 —— 至少现在尚未进行测试。
要启用代码覆盖率,请编辑scheme
的Test
操作,然后选中Options
标签下的Gather coverage for
复选框:
使用Command-U
运行所有测试,然后使用Command-9
打开Report navigator
。 选择该列表顶部项目下的Coverage
:
单击显示三角形以查看BullsEyeGame.swift
中的函数和闭包列表:
滚动到getRandomNumber(completion :)
以查看覆盖率为95.0%
。
单击此函数的箭头按钮以打开该函数的源文件。 当您将鼠标悬停在右侧栏中的coverage
注释上时,代码部分将突出显示绿色或红色:
覆盖率注释显示测试命中每个代码段的次数。 未调用的部分以红色突出显示。
1. Achieving 100% Coverage?
您应该努力争取100%
的代码覆盖率吗? 只是Google
的“100% unit test coverage”
,您会发现一系列支持和反对的论点,以及关于“100%覆盖率”这一定义的争论。 反对的说法是,最后10% - 15%
的努力是不值得的。 关于它的说法,最后10%– 15%
是最重要的,因为它很难测试。 谷歌查找“hard to unit test bad design”
,以找到令人信服的论点,即untestable code is a sign of deeper design problems。
您现在可以使用一些出色的工具来为项目编写测试。我希望这个iOS Unit Testing and UI Testing
教程能够给您信心,可以测试所有东西!
以下是一些需要进一步研究的资源:
-
WWDC
有几个有关测试主题的视频。WWDC17
的两个不错的选择是:Engineering for Testability 和Testing Tips & Tricks。 - 下一步是自动化:
Continuous Integration
和Continuous Delivery
。从我们的教程开始,Continuous Integration With GitHub, Fastlane & Jenkins 和Xcode Server for iOS: Getting Started。
然后,看看Apple的Xcode Server
的Automating the Test Process和xcodebuild
自动化测试过程,以及Wikipedia’s continuous delivery article,该文章借鉴了ThoughtWorks的专业知识。
- 如果您已经有一个应用程序但尚未编写测试,则可能要参考Working Effectively with Legacy Code by Michael Feathers,因为没有测试的代码是旧版代码!
- 乔恩·里德
(Jon Reid)
的Quality Coding
示例应用档案库非常适合了解有关Test-Driven Development的更多信息。
后记
本篇主要讲述了关于
Unit Testing
和UI Testing
,感兴趣的给个赞或者关注~~~