自动化Test使用详细解析(六) —— 关于Unit Testing 和 UI Testing(一)

版本记录

版本号 时间
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编写测试,但是可能不知道如何做。

您可能有一个正在运行的应用程序,但是您想测试为扩展该应用程序所做的更改。也许您已经编写了测试,但是不确定它们是否是正确的测试。或者,您已经开始开发新应用,并希望随时进行测试。

本教程将向您展示如何:

  • 使用XcodeTest 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()和示例测试方法定义XCTestCaseBullsEyeTests子类。

您可以通过三种方式运行测试:

  • 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")
}

guesstargetValue之间的差仍然是5,因此分数仍然应该是95

Breakpoint navigator中,添加Test Failure Breakpoint。 当测试方法发布故障断言时,这将停止测试运行。

运行您的测试,它应该在XCTAssertEqual行处停止,并显示测试失败。

检查调试控制台中的sut and gues

guesstargetValue − 5,但scoreRound105,而不是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中的XCTestExpectationdescription描述了您期望发生的事情。
  • 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 :)是否通过将targetValuestub伪造的数字进行比较来解析伪造的数据。

运行测试。它应该很快就能成功,因为没有任何实际的网络连接!

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中声明SUTmock对象:

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()
}

这将创建SUTmock对象,并将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 TestedBullsEye,然后接受默认名称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

代码覆盖率工具会告诉您测试实际在运行哪些应用程序代码,因此您知道该应用程序的哪些部分尚未进行测试 —— 至少现在尚未进行测试。

要启用代码覆盖率,请编辑schemeTest操作,然后选中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 IntegrationContinuous 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 TestingUI Testing,感兴趣的给个赞或者关注~~~

你可能感兴趣的:(自动化Test使用详细解析(六) —— 关于Unit Testing 和 UI Testing(一))