XCode中的单元测试:编写测试类和方法(内容意译自苹果官方文档)
当你在工程中通过测试导航栏添加了一个测试target之后, xcode会在测试导航栏中显示该target所属的测试类和方法。
这一章演示了怎么创建测试类,以及如何编写测试方法。
测试targets, 测试bundles, 以及测试导航栏
在开始创建测试类之前,测试导航栏值得多看上一眼。对于创建测试和完善测试工作来说,如何使用好它是很关键的。
将一个测试target加到工程会创建一个测试bundle.测试导航栏会展开测试bundles里面所有的源代码组成部分(在一个层级列表中展示了测试类和测试方法)。下面是一个工程的测试导航页面,该工程中有两个测试targets,该图展示了测试bundles、测试类、测试方法之间的嵌套层次关系。
测试bundles可以包含多个测试类。你可以使用测试类来将相关的测试分组,分组的方式可以通过功能或者其他的组织目的。例如,对于计算器例子工程,你可能会创建BasicFunctionsTests类、AdvancedFunctionsTests类、DisplayTests类,都包含在Mac_Calc_Tests测试bundle里(如下图)。
有些类型的测试可能会共享一些类型的setup或是teardown的需求,将它们合理的聚集在一起可以复用setup和teardown方法;如此一来会大大减少你的工作量,不失为一个机智的策略。
创建一个测试类
你可以使用测试导航栏中的+按钮来创建新的测试类。
你可以选择添加一个单元测试类或是一个UI测试类。选完以后,xcode会显示一个包含了一些文件模板项的选择页。该页面此时会默认选中一个Unit test case class。按下一步继续。
(此处截图省略)
所有的测试类都应该是XCTestCase的子类(由xctest框架提供)。
尽管xcode默认会将测试类的文件加到你的测试target的group中,你仍然可以自由的组织你的测试类文件(比如同时添加到多个targets中)。
测试类的结构
这里是OC编写的,想用swift来写也可以。
注意:setup 和 teardown不是必须的。如果一个类里的所有测试方法都需要相同的初始化和销毁代码,那么可以在setup 和 teardown中实现它们。这两个方法分别会在测试方法调用前和调用后被执行。你也可以实现同名的类方法(setup和teardown),它们会在所有测试方法执行前和执行后被调用(也就是说只执行一次,对象方法会根据测试方法的多少被执行多次)。
测试执行的流程
XCTest会找到所有的测试类并且运行每个类中所包含的所有测试方法。
注意:你也可以选择指定运行哪些XCTest测试。你可以使用test navigator禁用测试或者通过edit scheme来达到类似的目的。你也可以通过测试导航栏或代码编辑器的运行按钮来只运行一个测试或测试的子集。
对于每个类,测试开始时会先运行setup方法。对于每个测试方法,类的一个新对象被分配;其对象setup方法会被执行。在那之后它运行测试方法, 然后运行对象的teardown方法。当最后一个测试方法的teardown被调用以后,xcode会调用类的teardown方法,然后移动到下一个类。 这些步骤就这样机械化的被xcode来重复执行,直到所有类的所有测试方法都执行完毕。
编写测试方法
我们通过在测试类中编写测试方法来添加测试。测试方法是一个对象方法,其前缀是test,没有参数也没有返回值。我们通过测试方法来检查你的项目中得代码,观察其执行结果是否符合预期。如果代码不能产生预期的结果,我们通过一组断言api来报告故障。
要测试相应的项目代码,请先将对应的头文件加到你的测试类中。
当xcode运行测试时,它会独立调用每个测试方法。因此,每个方法必须准备和清理任何需要与主题API进行交互所需要的辅助变量,结构,和对象。如果这段代码是类中的所有测试方法都需要的,那你可以将其添加到setUp和tearDown对象方法中。
为异步操作编写测试
因为每个测试都是一个接一个独立的执行,所以测试方法都是同步执行的。但是越来越多的代码需要异步执行。为了应对异步方法和函数的测试需求,xctest在xcode6被大大增强了——在测试方法中支持序列化执行异步方法,具体实现是通过等待异步操作执行完毕或超时来达到这个目的。
示例代码如下图:
要了解更多关于编写异步测试的内容,请重点查看XCTest.framework中的XCTestCase+AsynchronousTesting.h头文件。
编写性能测试
性能测试通过重复运行一个你需要评估执行时间的代码block十次并收集的平均执行时间和标准差来完成。最后得到的平均值可以用来和一个参考基准来对比,以此得出成功或是失败的结论。
代码非常简单:
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
}];
}
一个实际应用的例子:
- (void) testAdditionPerformance {
[self measureBlock:^{
// set the initial state
[calcViewController press:[calcView viewWithTag: 6]]; // 6
// iterate for 100000 cycles of adding 2
for (int i=0; i<100000; i++) {
[calcViewController press:[calcView viewWithTag:13]]; // +
[calcViewController press:[calcView viewWithTag: 2]]; // 2
[calcViewController press:[calcView viewWithTag:12]]; // =
}
}];
}
性能测试运行时,可以展开代码编辑器中得选项来查看具体每次运行的结果。其中的基准线也是可以设置的。同时,性能测试还可以针对不同的设备来设置不同的基准线(根据设备的性能不同来制定)。该设定是非常合理也非常实用。
今天先写这么多,关键在实践。单元测试作为一个非常有用的工具,实在不应该将其束之高阁。大家可以对底层的基础代码多多编写单元测试来保证代码的可靠性和健壮性;同时单元测试也是重构过程中的一个得力武器,有了它的保证,代码的重构之路才能走的更加坦荡。
让我们拥抱变化。
To be continued… (未完待续)
待翻译的续篇:(执行测试,查看结果)
Running Tests and Viewing Results