做者:Maxime Defauw,原文连接,原文日期:2016-02-29
译者:ray;校对:numbbbbb;定稿:waygit
每一个 iOS 程序员都要时不时的为他们的 app 作 debug。除非你是那种超级大牛,不然你确定体验过查了无数个小时的 bug 最后才发现那仅仅是个简单的语法错误时那种油然而生的绝望感。或者更糟:你根本就没发现那些 bug。不管你是编程新手,仍是开发过不少 app 的老司机,例行的写写单元测试会让你的代码更可靠,更安全,更容易 debug!程序员
你很走运,Xcode 7 和 Swift 支持单元测试。尽管单元测试不保证(有了它你就会写出)绝对没有 bug 的 app,它仍是一种能让你验证每段代码是否如期工做,并让 debug 过程更加便利。github
正如其名,在单元测试中你要为某段代码单元建立一些小规模的、针对其某个特性的测试,而后确保每一个代码单元都能经过这些测试。若是经过的话,它的旁边会出现一个绿色小标志,而若是因故测试不经过, Xcode 会把该测试标记为 "failed"。这就提示你去查看代码,找出失败缘由。express
首先下载这个我为你准备的 starting project。一个短小精悍的 app:它会对一个给定的数字和百分比作一个乘法计算。(好比80的10%是8。)编程
这个 PercentageCalculator 项目很是简单。你惟一须要关注的就是 ViewController.swift 这个文件。里面的代码都标记了注释,很容易理解。swift
有 5 个 IBOutlets:每个都对应了屏幕上一个 UIElement,除 title(标题)以外,还有 2 个 slider 对应 2 个 IBActions。每一个 IBAction 的方法名都精确描述了其用途及将要执行的操做。当一个 slider 值改变时,其对应着的百分比或数字的值也会随之改变。安全
还有两个简单的函数 “updateLabels()” 和 “percentage()” 作了符合期待的事情:当一个 slider 改变时第一个函数更新 label,第二个函数获取两个浮点数并返回百分比的计算结果。app
在模拟器中运行 app。刚开始一切看起来都很正常。但当你开始改变数字时就会发现计算结果有问题。为找到 bug,咱们将代码分割成不一样的单元,而后分别作测试,看看每一个是否都如期运行。这不会解决 bug,但能缩小你的查找范围。框架
我建立项目的时候,默认状况下会勾选建立一个 test 文件的选项(若是你想要手动加一个的话,在 iOS Source 下面选择 select File > New > File > Unit Test Case Class)。咱们的例子中 test 文件已经被 Xcode 自动建立出来,能够在项目导航栏中 “PercentageCalculatorTests” 文件夹中找到它。异步
在 PercentageCalculatorTests.swift
文件中,PercentageCalculatorTests
类里面已经为咱们建立好了 4 个方法。其中 2 个是测试方法(test methods)的例子,你能够删掉它们(它俩都以 test
关键字开头,而且它们左边的竖条中都有个方块形图标,名字也都以 “...Example” 结尾,因此你能够经过这些辨识出来它们是测试方法)。另外两个方法,setUp()
和 tearDown()
是特殊的样板方法(boilerplate methods),它们分别在每一个测试方法被执行以前,和每一个测试方法被执行以后被执行。
如今是时候写你的第一个单元测试函数了!本教程咱们只测试 ViewController
类,须要在 PercentageCalculatorTests
中添加一个它的实例。
class PercentageCalculatorTests: XCTestCase { var vc: ViewController! override func setUp() { super.setUp() // 这里写setup的代码。本class里每一个测试函数被调用以前该方法都会被先调用。 } override func tearDown() { // 这里写teardown的代码。本class里每一个测试函数被调用以后该方法都会被调用。 super.tearDown() } }
PercentageCalculatorTests
是一个 XCTestCase
的子类,后者被打包在 XCTest 框架中。每个 XCTestCase
子类的实例都负责对你项目的某个特定部分作测试,好比对一个特性作测试。
在 setup 方法中实例化一个 vc
。这样对每个测试方法你都会获得一个“全新的” ViewController
实例,由于在每一个测试方法执行前 setUp()
都会被调用一次。把 setUp()
方法修改以下:
override func setUp() { super.setUp() let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) vc = storyboard.instantiateInitialViewController() as! ViewController }
如今你应该记得全部的测试方法的名字都要以 test
关键字开头,不然 Xcode 不会识别。添加一个新的 testPercentageCalculator()
测试方法,来验证一下 ViewController
中的 percentage()
工做是否正常。
func testPercentageCalculator() { }
单元测试中你要去检查某段代码是否如你所愿的那样工做。待测试的代码段通常都只有几行,典型状况是你只须要测试一个方法或者一个函数。单元测试是这样去作的:你给某个代码单元一个输入值,让这个值过一遍这段代码,而后检查一下输出的值是否和预期的同样。
与“咱们指望的那个值”作比较的这部分由 XCTAssert
函数来处理。最简单的 XCTAssert
函数是XCTAssert(expression: BooleanType)
。这个函数要求一个布尔表达式(相似于 5>3
,8.90 == 8.90
或者 true
这种),随后若是表达式为真则让测试经过,不然认为测试失败。
尝试一下!首先给 testPercentageCalculator()
方法加添加下面一行。而后把光标移到方法名左边侧栏的那个方块图标上,停下光标以后方块图标变成了一个执行光标,点击一下就开始了测试。
func testPercentageCalculator() { XCTAssert(true) }
若是一切顺利,则测试经过,方法左边会出现一个绿色检测标。
如今来真的:测试 percentage()
方法!用 ViewController
的一个实例 - vc
属性来调用这个方法。给这个方法两个浮点数,好比 50 和 50,而后把结果存储到常量 p
中。这个例子中 p
应该是 25(50 的 50% 是 25)。而后用 XCTAssert(p == 25)
检测一下是否是这样,执行测试方法。把 testPercentageCalculator()
改为这样:
func testPercentageCalculator() { // 应该是25 let p = vc.percentage(50, 50) XCTAssert(p == 25) }
测试成功了,这意味着 ViewController
的 percentage()
函数工做正常,咱们应该在其余的地方继续寻找 bug。也许 bug 在 updateLabels()
里面?
如今添加一个新的测试方法 testLabelValuesShowedProperly()
来验证一下 label 能不能正确的显示 text。和以前同样,调用 ViewController 的一个方法 - 这回是 updateLabels()
- 而后看看每一个标签的 text
属性和咱们指望的那个 text 是否相同。
注意到你要给 XCTAssert
函数传一个新的参数:一个 string 类型的消息。这对咱们此次要对多个值作检查(调用三次 XCTAssert )来完成测试而言就会很方便。若是测试失败,这条消息就会指名咱们具体是哪里错了。
func testLabelValuesShowedProperly() { vc.updateLabels(Float(80.0), Float(50.0), Float(40.0)) // labels应该显示80, 50 and 40 XCTAssert(vc.numberLabel.text == "80.0", "numberLabel doesn't show the right text") XCTAssert(vc.percentageLabel.text == "50.0%", "percentageLabel doesn't show the right text") XCTAssert(vc.resultLabel.text == "40.0", "resultLabel doesn't show the right text") }
你尝试执行这个测试方法时,会收到编译器的错误提示:numberLabel
,percentageLabel
和 resultsLabel
是 nil
。怎么回事呢?
我是在 storyboard 文件中建立了这些 labels 的,所以只有当 view 被加载以后(loaded)它们才会被初始化,然而因为对单元测试来讲 loadView()
方法不会被触发,因此这些 labels 没有被建立,只能是 nil
。一种可能的方法是经过调用 vc.loadView()
来解决,可是 Apple 在它的文档中并不推荐你这么作,由于当已经被加载的对象又被加载一次的话可能会引发内存泄露。
正确的方法是你应该先访问一下 vc
的 view
这个属性,这会让 vc
反过来触发全部相应的方法,不只仅包括 loadView()
。把 testLabelValuesShowedProperly()
改为这样:
func testLabelValuesShowedProperly() { let _ = vc.view vc.updateLabels(Float(80.0), Float(50.0), Float(40.0)) // labels应该显示80, 50 and 40 XCTAssert(vc.numberLabel.text == "80.0", "numberLabel doesn't show the right text") XCTAssert(vc.percentageLabel.text == "50.0%", "percentageLabel doesn't show the right text") XCTAssert(vc.resultLabel.text == "40.0", "resultLabel doesn't show the right text") }
注意到下划线(_)忽略了常量的名字。由于咱们实际上并不须要用到这个 view。加下划线就是告诉编译器“你伪装访问一下这个 view,把相应的方法触发就行。”
执行测试。(若是想一并执行咱们test类的全部测试,你还能够点击 “class PercentageCalculatorTests” 旁边的那个方块)。
如你所见,测试失败了!咱们给 XCTAssert 方法传入的错误细节消息帮助咱们快速识别出引发 bug 的可能缘由。此次测试告诉咱们 resultsLabel 没有显示出正确的文本,因此咱们进到 ViewController 里看看对这些 label 的 text 值是在那里被设置的。仔细看了 ViewController.swift
的 updateLabels()
代码以后,咱们发现了 bug 的缘由:
self.resultLabel.text = "\(rV + 10)"
应该是:
self.resultLabel.text = "\(rV)"
更新代码以后再运行一次测试,一切都应该正常了!
本篇教程中你学到了 Xcode 中的单元测试的相关内容,以及它怎样可以帮你找到代码中的 bug。除了预防 bug 以外,单元测试还能够用来作性能测试和异步测试。还可能让你感兴趣的是UI测试,你能够录制下你在 app 上作出的动做来测试你的 app 在实际使用情景下是如何表现的。若是听起来以为感兴趣,那必定要看看这个讲 UI 测试的 WWDC视频。
项目的最终版本能够在 Github上下载。
若是你有关于 UI 测试的任何问题,或者学习本教程中遇到了困难,请在评论中点我!
做者介绍:Maxime Defauw 是一个有经验的程序员,在 App Store 和 Google Play store 上发布过多个 app。他今年 16 岁,居住在比利时。最近他在 San Francisco 举行的 WWDC15 上得到了 Apple 的奖学金。Max 熟练掌握 Objective-C,C,C#,如今是 Swift。不码代码的时候他通常在曲棍球场或者高尔夫球场上。在 Twitter 上 @MaximeDefauw 粉他。
本文由 SwiftGG 翻译组翻译,已经得到做者翻译受权,最新文章请访问 http://swift.gg。