前言
除了断言,还有更多的测试。XCTest 什么时候创建和运行测试?iOS程序员特别容易对测试生命周期做出错误的假设。这些假设会导致测试设计中的错误。
比如我们经常遇到的,一个测试用例,在单独运行的情况下可以测试通过,但是在项目组合测试中失败,为了避免不稳定的测试,我们需要给测试一个稳定,干净的环境.
测试方法中的代码组织成三个部分来明确这些阶段:
- 安排、Arrange 安排,它是定义所有变量和模型的部分。
- 行动、Action 行为:是触发被测方法并返回结果的部分。
- 断言` Assert 断言:这是评估预期结果的部分。
记住AAA 是单元测试的重要部分
XCTest的使用
func test_methodOne() {
let sut = MyClass()
sut.methodOne()
// Normally, assert something
}
名称sut代表被测系统,通常缩写为SUT。这是“我们正在测试的东西”的常用术语。与这个简单的例子不同,测试通常有很多对象在起作用。使用像sut这样一致的名称可以清楚地表明测试将作用于哪个对象。它还可以更轻松地重用测试代码片段。
XCTest 测试机制
- XCTest 在测试时候,搜索从XCTestCase继承的所有类。
- 对于这样的类,它会找到每个测试方法。这些方法的名称以test开头,没有参数,也没有返回值。如
func test_methodOne()
- 对于每个这样的测试方法,它都会创建一个类的实例。使用 Objective-C runtime,它会记住该实例将运行哪个测试方法。
- XCTest 将子类的实例收集到测试套件中。
- 当它完成所有测试用例的创建后,XCTest 才会开始运行它们。
class MyClassTests: XCTestCase {
private let sut = MyClass()
func test_methodOne() {
sut.methodOne()
// Normally, assert something
}
func test_methodTwo() {
sut.methodTwo()
// Normally, assert something
}
}
举例
MyClassTests
有两个测试方法 test_methodOne
,test_methodTwo
。
那么,在XCTest
运行时, 会找到MyClassTests
。它搜索以test
开头的方法名称,并找到两个。所以它创建了MyClassTests
的两个实例:一个实例运行test_methodOne
,另一个运行test_methodTwo
。
使用setUp()
,tearDown()
优化
XCTestCase定义了两个方法,setUp和tearDown,这两个方法设计为在子类中被覆盖。
XCTest 中的测试运行程序保证每个测试用例的顺序如下:
- 呼叫的setUp(创建对象)。
- 调用测试方法。
- 调用tearDown(销毁)。
class MyClassTests: XCTestCase {
private var sut: MyClass!
override func setUp() {
super.setUp()
sut = MyClass()
}
override func tearDown() {
sut = nil
super.tearDown()
}
func test_methodOne() {
sut.methodOne()
// Normally, assert something
}
func test_methodTwo() {
sut.methodTwo()
// Normally, assert something
}
}
注意: 初始化存储属性的测试类中。要将这些属性从let
更改为var
。并且添加!
Tips 检测测试Log
通过如图所示,我们可以快速找到test的失败消息
截屏2021-07-07 下午8.03.37.png
如何写好项目里的测试?
代码测试覆盖率
在 Xcode 菜单中,选择Product
▶ Scheme
▶ Edit Scheme
...或按 ⌘
- <
。
现在我们就设置好了测试覆盖率
⌘
+ U
试一下
并且 Xcode 菜单中选择Editor ▶ Code Coverage。
截屏2021-07-07 下午9.23.06.png红色区域的数字代表我们这段代码测试了几次,在红色条纹区域。将鼠标光标悬停在该区域,您会看到情况发生了变化,如下所示:
截屏2021-07-07 下午9.24.42.png绿色部分显示了我们接触过的代码。带有else
的那一行部分是绿色的,部分是红色的。这为我们提供了一种查看行内代码覆盖率的方法。
为现有代码添加测试
举例
class CoveredClass {
static func max(_ x: Int, _ y: Int) -> Int {
if x < y {
return y
} else {
return x
}
}
}
如果代码正在使用中,我们就不需要从需求逆向工作。相反,我们可以编写有效地使用遗留代码称为特性测试的内容。这些是捕获代码实际行为的测试。
要编写特性测试,请执行以下操作:
- 从测试中调用代码,产生某种结果。
- 编写一个断言,将结果与您知道不匹配的值进行比较。
- 运行测试。失败消息将告诉您实际结果。
- 调整断言,使其预期实际结果。
- 重新运行测试以查看它是否通过。
1,2部分
func test_max_with1And2_shouldReturnSomething() {
let result = CoveredClass.max(1, 2)
XCTAssertEqual(result, -123)
}
截屏2021-07-07 下午9.33.33.png
3.运行测试。这给了我们一条失败消息
4.我们从失败消息中复制实际值2并粘贴到断言中
func test_max_with1And2_shouldReturn2() {
let result = CoveredClass.max(1, 2)
XCTAssertEqual(result, 2)
}
5.运行
截屏2021-07-07 下午9.37.40.png我们本次的测试就完成了!!! 通过~
当然,如果你想要更好的测试覆盖率,让我们添加一个测试来覆盖后半部分。
条件是if x < y
func test_max_with3And2_shouldReturn3() {
let result = CoveredClass.max(3, 2)
XCTAssertEqual(result, 3)
}
这应该给我们 100%
的覆盖率。但是实际上,会由于有个}
,很难做到100%
覆盖率
多写几个,也是可以完成100% 覆盖率的,但是,我们不应该成为测试覆盖率的奴隶.
Tips
一个好的测试名称包含三个部分:
- 测试的内容是什么。这通常是一个函数名。
- 测试条件。有什么不同的输入?
- 预期的结果。
网友评论