MENU

Xcode7中使用Swift进行单元测试

作者:MAXIME DEFAUW,时间:2015/2/29
翻译:BigNerdCoding, 如有错误欢迎指出。原文链接

对于每一个iOS程序员来说都不可避免的需要对自己的应用进行调试。除非你是一个不世出的编程天才,不然由于没有进行调试而导致了一个简单的语法错误,并为之花费数小时去修复查找的时候你一定是绝望而崩溃的。甚至可能会更糟,你永远的都没有发现那个bug。无论你是刚刚入门的菜鸟还是老司机,定期进行单元测试会让你的代码变得更加可靠、安全、更容易调试!

很幸运,Xcode7和Swift语言支持单元测试。虽然单元测试并不一定意味这消灭应用中的所有bug,但是对于确保每一块或者每一单元的代码都能正确工作以及简化调试过程来说这依旧是一个强有力的方法。

顾名思义,在单元测试中你会建立一些小而具体的功能去测试某一段具体的代码并确保每一个单元都通过了测试。如果代码通过测试的话,其后面会出现一个绿色的logo。不管由于什么导致测试没有通过,Xcode会标记该测试"failed"。该标志是为了让你去从代码中更好的找到测试失败的原因。

Demo的简单介绍

首先去下载我已经为大家建好的起始项目。这是一个很简单的应用,该应用会计算给定数字的百分比是多少(例如:80的10%是8)。

这个PercentageCalculator工程相当简单,唯一一个你需要重点关注的文件就是ViewController.swift。该文件中带有注释并且代码也很简单直接。

文件中有五个IBOutlets:除了标题外,每一个都对应屏幕上的UIElement。还有两个IBAction对应两个滚动条,每个IBAction的名称都很好的解释了自己需要做的事情。当两个滚动条中的任何一个值发生变化的时候,对应的百分比或者数字都会发生响应的改变。

更进一步,文件中还有两个简单的函数数updateLabels()precentage()。这两个函数的功能如下:前者当滑块变动时更新所有的labels,后者根据两个浮点数来计算对应的百分比。

在你的模拟器中运行该应用。首先你会觉得一起正常,但是当你开始改变滚动条的时候,你会发现计算得到的结果是错的。为了找到这个bug,我们可以按单元来划分我们的代码并且分别对其进行测试看看是否按预想的那样。这不能解决bug,当时能缩小问题可能出现的查找范围。

01

当我在创建工程的时候,我默认选择了Include Uint Tests。当然你也可以手动添加,在iOS Source下选择File -> New -> File -> Unit Test Case Class。在起始项目中,文件能在导航栏 PercentageCalculatorTests文件夹中找到。

02

PercentageCalculatorTests.swift文件中PercentageCalculatorTests类已经创建了四个函数。其中的两个测试方法作为示例你可以删除(它们以关键字 test 开头又以 ...Example 结尾并且还有一个菱形图标)。另外两个方法是setUp()tearDown,它们分别会在单元测试方法执行前后分别被调用。

开始编写单元测试代码

现在我们可以编写第一个单元测试函数了。在这篇教程中,我们只会对ViewController类进行测试,所以我们需要在PercentageCalculatorTests类中添加一个实例。

class PercentageCalculatorTests: XCTestCase {
    var vc: ViewController!
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
}

PercentageCalculatorTestsXCTestCase的一个子类。该类来源于XCtext框架。每一个XCTestCase的一个子类的实例都负责你工程中某个特定部分的测试,例如某个特定的特征。

setUp()方法里面初始化vc。该函数在你每次调用测试的时候会让你获得最新的ViewController,因为该函数会在测试函数调用之前被调用。修改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() {
    
}

在单元测试中你能检查一段代码师傅正确工作。通常情况下测试代码只有几行,用于测试一个函数或者方法。单元测试。给单元测试部分的代码提供一个输入值,然后运行代码,测试得到的输出结果是不是我们期望的。

03

对于“是否是所期望的结果”的比较在XCTAssert函数中进行处理。最简单的函数调用是直接传入一个
布尔变量XCTAssert(expression: BooleanType)。通过传入的这个布尔表达式(像:5 > 3, 8.90 == 8.90 or true)的值是不是为true来判断是非通过测试。首先,我们在函数testPercentageCalculator中加入以下代码试试看。然后移动鼠标指向左侧的菱形图标上。当你点击开始测试并将鼠标悬停在该图标上面该图标会转变为一个执行图标。

func testPercentageCalculator() {
    XCTAssert(true)
}

如果一切顺利,测试会通过并且图标会变成一个绿色的复选标记。

04

验证百分比的计算

现在开始真正的测试:检查percentage()函数!使用ViewController实例vc属性调用该函数。传入两个浮点型变量,例如50和50,然后将结果存到const常量p中。然后使用XCTAssert(p == 25)检查的到的结果是不是25(50的50%)。修改testPercentageCalculator()函数中的代码:

func testPercentageCalculator() {
    let p = vc.percentage(50, 50)
    XCTAssert(p == 25)
}

测试通过了,说明percentage()没有任何问题,我们可以去其它地方查找bugs。会不会在updateLabels()处呢。

验证Labels

现在,添加函数testLabelValuesShowedProperly()并在里面调用ViewController中的updateLabels()函数用来验证labels是否正确的显示了内容。

注意,你需要在XCTAssert函数里面添加一个失败消息的字符串作文新参数。如此处理是因为我们需要知道当测试没有通过的时候到底是那些地方出现的问题毕竟这里会调用三次XCTAssert函数。

func testLabelValuesShowedProperly() {
    vc.updateLabels(Float(80.0), Float(50.0), Float(40.0))
        
    // The labels should now display 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、percentageLabel 都是nil。怎么可能会出现这种情况呢?

我已经在storyboard中已经建好了这些lables,并且在视图加载的时候就已经进行了初始化任务,但是在单元测试部分loadView()方法永远都不会触发因此这些值都是空值。一个可能的解决方法是我们自己调用vc.loadView(),但是这种做法官方文档里面并不提倡,因为这种做法可能导致内存泄漏(当视图进行了多次加载的时候)。

正确的解决方法是,访问vcview属性。该做法会触发所有需要的方法,不仅仅只有loadView()
对测试函数进行如下修改:

func testLabelValuesShowedProperly() {
    let _ = vc.view
        
    vc.updateLabels(Float(80.0), Float(50.0), Float(40.0))
        
    // The labels should now display 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")
}

注意到我们使用下划线_来代表一个常量名。这是因为我们并不真正需要这个常量也永远不会使用这个常量。它只是用来告诉编译器”假装访问并触发视图的所有方法。“

执行测试。(如果你想执行所有测试的话,你可以在PercentageCalculatorTests类左边点击图标执行)。

修复程序中的Bugs

正如你所见到的一样,测试失败了。我们提供的错误信息很好的帮助了找到潜在的bug来源。错误信息显示resultsLabel没有正确显示结果。因为我们直接跳转到ViewController中,找到设置显示值的地方。当你仔细查找之后你回发现bug就隐藏在updateLabels()函数里面。

self.resultLabel.text = "\(rV + 10)"

应该是:

self.resultLabel.text = "\(rV)"

更新代码然后再次运行测试。一切都美好了。

总结

在这篇简单的教程中,你可以学会在Xcode中进行单元测试以及它是如何帮助你找到代码中的bug的。出了检查bug外,单元测试还可以进行性能和异步测试。另外一个你可能感兴趣的是UI测试。如果你真的很干兴趣的话可以去看这个UI测试的WWDC视频