Swift学习

自动化Test使用详细解析(二) —— 单元测试和UI Test

2019-04-19  本文已影响95人  刀客传奇

版本记录

版本号 时间
V1.0 2019.04.19 星期五

前言

自动化Test可以通过编写代码、或者是记录开发者的操作过程并代码化,来实现自动化测试等功能。接下来几篇我们就说一下该技术的使用。感兴趣的可以看下面几篇。
1. 自动化Test使用详细解析(一) —— 基本使用(一)

开始

首先看下写作环境

Swift 4.2, iOS 12, Xcode 10

本篇主要了解如何将单元测试和UI测试添加到iOS应用程序,以及如何检查代码覆盖率。

写作测试不是很迷人,但是由于测试可以防止让你的闪亮应用程序变成了一个充满bug的垃圾,这是必要的。 如果您正在阅读本教程,您已经知道您应该为您的代码和UI编写测试,但您可能不知道如何去做。

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

本教程将向您展示:

在此过程中,您将获得测试ninjas所使用的一些词汇。


Figuring Out What to Test

在编写任何测试之前,了解基础知识非常重要。 你需要测试什么?

如果您的目标是扩展现有应用程序,则应首先为计划更改的任何组件编写测试。

通常,测试应包括:

1. Best Practices for Testing

首字母缩略词FIRST描述了有效单元测试的一套简明标准。 这些标准是:

遵循FIRST原则将使您的测试保持清晰且有用,而不是为您的应用程序设置障碍。

打开已经下载的工程文件:


Unit Testing in Xcode

Test navigator提供了最简单的测试方法,您将使用它来创建测试目标并针对您的应用程序运行测试。

1. Creating a Unit Test Target

打开BullsEye项目并按Command-6打开Test navigator

单击左下角的+按钮,然后从菜单中选择New Unit Test Target ...

接受默认名称BullsEyeTests。 当test bundle出现在Test navigator中时,单击以在编辑器中打开该包。 如果bundle未自动显示,请单击其他导航器之一进行故障排除,然后返回Test navigator

默认模板导入测试框架XCTest,并使用setUp()tearDown()和示例测试方法定义XCTestCaseBullsEyeTests子类。

运行测试有三种方法:

您还可以通过在Test navigatorgutter中单击其菱形来运行单个测试方法。

尝试不同的方式来运行测试,以了解它需要多长时间以及它看起来像什么。 样本测试还没有做任何事情,所以它们运行得非常快!

当所有测试成功后,菱形将变为绿色并显示复选标记。 您可以单击testPerformanceExample()末尾的灰色菱形以打开性能结果:

本教程不需要testPerformanceExample()testExample(),因此请删除它们。

2. Using XCTAssert to Test Models

首先,您将使用XCTAssert函数来测试BullsEye模型的核心功能:BullsEyeGame对象是否正确计算了一轮的分数?

BullsEyeTests.swift中,在import语句下方添加以下行:

@testable import BullsEye

这使得单元测试可以访问BullsEye中的internal类型和功能。

BullsEyeTests类的顶部,添加以下属性:

var sut: BullsEyeGame!

这为BullsEyeGame创建了一个占位符,它是System Under Test (SUT),或者是测试用例类与测试有关的对象。

接下来,用这个替换setup()的内容:

super.setUp()
sut = BullsEyeGame()
sut.startNewGame()

这会在类级别创建BullsEyeGame对象,因此此测试类中的所有测试都可以访问SUT对象的属性和方法。

在这里,您还可以调用游戏的startNewGame()来初始化targetValue。 许多测试将使用targetValue来测试游戏是否正确计算得分。

在您忘记之前,请在tearDown()中释放您的SUT对象。 将其内容替换为:

sut = nil
super.tearDown()

注意:最好在setUp()中创建SUT并在tearDown()中释放它,以确保每个测试都以一个干净的平板开始。 有关更多讨论,请查看Jon Reid’s post关于此主题的帖子。


Writing Your First Test

现在,您已经准备好编写第一个测试了!

将以下代码添加到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开头,然后是对其测试内容的描述。

将测试格式化为given, when and then的部分是一种很好的做法:

单击gutterTest navigator中的菱形图标运行测试。 这将构建并运行应用程序,菱形图标将变为绿色复选标记!

注意:要查看XCTestAssertions的完整列表,请转到 Apple’s Assertions Listed by Category

1. Debugging a Test

BullsEyeGame故意内置了一个错误,你现在就可以练习找到它。 要查看操作中的bug,您将创建一个测试,从given部分中的targetValue中减去5,并使其他所有内容保持不变。

添加以下测试:

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

guesstargetValue之间的差异仍为5,因此得分仍应为95

在“断点”导航器Breakpoint navigator中,添加Test Failure Breakpoint。 当测试方法发布失败assertion时,这将停止测试运行。

运行测试,它应该在测试失败时停在XCTAssertEqual行。

在调试控制台中检查sutguess

guesstargetValue - 5但是scoreRound105,而不是95

要进一步调查,请使用正常的调试过程:在when语句中设置一个断点,在BullsEyeGame.swift中设置一个断点,在check(guess :)中,它会产生difference。 然后再次运行测试,并跳过let difference语句以检查应用程序中difference的值:

问题是difference是负值,所以得分是100 - ( - 5)。 要解决此问题,您应该使用difference的绝对值。 在check(guess :)中,取消注释正确的行并删除不正确的行。

删除两个断点,然后再次运行测试以确认它现在成功。

2. Using XCTestExpectation to Test Asynchronous Operations

现在您已经学会了如何测试模型和调试测试失败,现在是时候继续测试异步代码了。

打开HalfTunes项目。 它使用URLSession来查询iTunes API并下载歌曲样本。 假设您要修改它以使用AlamoFire进行网络操作。 要查看是否有任何中断,您应该为网络操作编写测试并在更改代码之前和之后运行它们。

URLSession方法是异步的:它们立即返回,但直到稍后才完成运行。 要测试异步方法,请使用XCTestExpectation使测试等待异步操作完成。

异步测试通常很慢,因此您应该将它们与更快的单元测试分开。

创建一个名为HalfTunesSlowTests的新单元测试目标。 打开HalfTunesSlowTests类,并在现有import语句下方导入HalfTunes应用程序模块:

@testable import HalfTunes

此类中的所有测试都使用默认的URLSession将请求发送到Apple的服务器,因此声明一个sut对象,在setUp()中创建它并在tearDown()中释放它。

用以下内容替换HalfTunesSlowTests类的内容:

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发送有效查询是否返回200状态代码。 大多数代码与您在应用程序中编写的代码相同,使用以下附加行:

运行测试。 如果您已连接到互联网,则应用程序在模拟器中加载后,测试应该需要大约一秒钟才能成功。

3. Failing Fast

失败会伤害,但它不需要永远。

要体验失败,只需从URL中的“itunes”中删除's'即可:

let url = 
  URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

运行测试。 它失败了,但需要完整的超时间隔! 这是因为你假设请求总是成功,那就是你调用promise.fulfill()的地方。 由于请求失败,仅在超时到期时才完成。

您可以通过更改以下假设来改进此问题并使测试失败:不要等待请求成功,而是等待直到调用异步方法的完成处理程序。 一旦应用程序收到来自服务器的响应(OK或错误),就会发生这种情况,这符合预期。 然后,您的测试可以检查请求是否成功。

要了解其工作原理,请创建一个新测试。

但首先,通过撤消对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)
}

关键的区别在于,简单地输入完成处理程序就能满足期望,而这只需要大约一秒钟的时间。 如果请求失败,则then断言失败。

运行测试。 它现在应该花费大约一秒钟才能失败。 它失败是因为请求失败,而不是因为测试运行超过了timeout

修复url,然后再次运行测试以确认它现在成功。

后记

本篇主要介绍了单元测试和UI Test使用简单示例,感兴趣的给个赞或者关注~~~

上一篇下一篇

猜你喜欢

热点阅读