当使用测试导航器将测试目标添加到项目时,Xcode在测试导航器中显示该目标的测试类和方法。在测试目标中是包含测试方法的测试类。本章介绍如何创建测试类和编写测试方法。
测试目标,测试包和测试导航器
在查看创建测试类之前,值得再看一下测试导航器。使用它是创建和使用测试的核心。
向项目添加测试目标创建测试包。测试导航器列出项目中所有测试包的源代码组件,在分层列表中显示测试类和测试方法。下面是一个具有两个测试目标的项目的测试导航器视图,显示了测试包,测试类和测试方法的嵌套层次结构。
测试包可以包含多个测试类。你可以使用测试类将测试分为相关组,无论是出于功能还是组织目的。例如,对于计算器的例子项目可以创建BasicFunctionsTests,AdvancedFunctionsTests和DisplayTests类,各部分Mac_Calc_Tests测试包。
一些类型的测试可能共享某些类型的设置和拆卸要求,使得将这些测试集中到类中是明智的,其中一组设置和拆卸方法可以最小化为每个测试方法编写多少代码。
创建测试类
注意: 本章着重介绍单元测试类和方法。创建UI测试的目标,类和方法,以及如何从单元测试工作不同,在讨论用户界面测试。
你可以使用测试导航器中的添加按钮(+)来创建新的测试类。
你可以选择添加单元测试类或UI测试类。选择其中之一后,Xcode会显示一个文件类型选择器,其中选择了所选文件模板类型。“新建单元测试类”模板在下图中突出显示。单击下一步继续你的选择。
你添加的每个测试类都会根据你在配置表中输入的测试类名称,将一个名为TestClassName .m 的文件添加到项目中。
注意: 所有测试类都是XCTest框架XCTestCase提供的子类。
尽管默认情况下Xcode将测试类实现文件组织到为项目的测试目标创建的组中,但是你可以根据自己的选择组织项目中的文件。当你按下一步按钮时,标准Xcode添加文件表遵循此配置。
注意: 创建新项目时,默认情况下将为你创建测试目标和关联的测试包,其名称来自项目名称。例如,创建名为的新项目会MyApp自动生成名为的测试包MyAppTests和MyAppTests使用关联的MyAppTests.m实现文件命名的测试类。
测试类结构
测试类具有以下基本结构:
#import <XCTest/XCTest.h>
@interface SampleCalcTests : XCTestCase
@end
@implementation SampleCalcTests
- (void)setUp {
[super setUp];
// 在调用类中的每个测试方法之前调用此方法。
}
- (void)tearDown {
//此方法在调用类中的每个测试方法后调用。
[super tearDown];
}
- (void)testExample {
//这是一个功能测试用例的例子。
//使用XCTAssert和相关函数来验证测试是否产生正确的结果。the correct results.
}
- (void)testPerformanceExample {
//这是一个性能测试用例的例子。
[self measureBlock:^{
//放你想要测量的代码这里的时间。
}];
}
@end
测试类在本例中是在Objective-C中实现的,但也可以在Swift中实现。
注意: 本文中的实现示例都是使用Objective-C编写的,以保持一致性。
Swift与使用XCTest和实现测试方法完全兼容。也可以使用所有Swift和Objective-C跨语言实现功能。
请注意,实现包含用于实例设置和拆卸的方法,具有基本实现; 这些方法不是必需的。如果类中的所有测试方法都需要相同的代码,你可以自定义setUp并tearDown包括它。你添加的代码在每个测试方法运行之前和之后运行。你可以选择为类setup(+ (void)setUp)和teardown(+ (void)tearDown)添加自定义的方法,它们在类中的所有测试方法之前和之后运行。
测试执行流程
在默认情况下,当运行测试时,XCTest将查找所有测试类,并为每个类运行其所有测试方法。(所有测试类继承自XCTestCase。)
注意: 有些选项可用于更改XCTest运行的测试。你可以使用测试导航器或编辑方案禁用测试。你还可以使用测试导航器或源代码编辑器水槽中的运行按钮运行组中的一个测试或一组测试。
对于每个类,测试从运行类设置方法开始。对于每个测试方法,将分配该类的一个新实例,并执行其实例设置方法。之后它运行测试方法,然后是实例拆卸方法。该序列对于类中的所有测试方法重复。在类中的最后一个测试方法拆卸已经运行之后,Xcode执行类拆卸方法并移动到下一个类。此序列重复,直到所有测试类中的所有测试方法都已运行。
写测试方法
通过编写测试方法将测试添加到测试类。测试方法是测试类的实例方法,以前缀“test”开始,不带参数,并返回void,例如(void)testColorIsRed()。测试方法在项目中执行代码,如果代码没有产生预期结果,则使用一组断言API报告失败。例如,函数的返回值可能与预期值进行比较,或者测试可能断言在一个类中不当使用方法会抛出异常。XCTest断言描述了这些断言。
对于测试方法来访问要测试的代码,将相应的头文件导入到测试类中。
当Xcode运行测试时,它独立调用每个测试方法。因此,每个方法必须准备和清理与主题API交互所需的任何辅助变量,结构和对象。如果此代码对类中的所有测试方法都是通用的,那么可以将其添加到
测试类结构中描述
的必需setUp和tearDown实例方法。
这里是单元测试方法的模型:
- (void)testColorIsRed {
//设置,调用测试主题API。(代码可以在setUp方法中共享。)
//测试逻辑和值,断言报告通过/失败到测试框架。
// 拆除。(代码可以在tearDown方法中共享。
}}
下面是一个简单的测试方法示例,用于检查是否CalcView为SampleCalc成功创建了实例,该应用程序显示在快速入门章节中:
- (void)testCalcView {
// 建立
app = [NSApplication sharedApplication];
calcViewController =(CalcViewController *)[NSApplication sharedApplication] delegate];
calcView = calcViewController.view;
XCTAssertNotNil(calcView,@“找不到CalcView实例”);
//没有拆卸需要
}}
写异步操作测试
测试同步执行,因为每个测试独立地被一个接一个地调用。但越来越多的代码异步执行。为了处理调用异步执行方法和函数的测试组件,XCTest在Xcode 6中得到了增强,包括通过等待异步回调或超时的完成来串行化测试方法中的异步执行的能力。
源示例:
//测试文档是否打开。因为打开是异步的,
//使用XCTestCase的异步API等待文档
//完成打开。
- (void)testDocumentOpening
{
//创建期望对象。
//这个测试只有一个,但是可以等待多个期望。
XCTestExpectation * documentOpenExpectation = [self expectationWithDescription:@“document open”];
NSURL * URL = [[NSBundle bundleForClass:[self class]]
URLForResource:@“TestDocument”withExtension:@“mydoc”];
UIDocument * doc = [[UIDocument alloc] initWithFileURL:URL];
[doc openWithCompletionHandler:^(BOOL success){
XCTAssert(success);
//可能在文档打开后在这里关于其他事情...
//满足期望 - 这将导致-waitForExpectation
//调用它的完成处理程序,然后返回。
[文档打开Expectation履行];
}];
//测试将在这里暂停,运行运行循环,直到超时被击中
//或所有期望得到满足。
[self waitForExpectationsWithTimeout:1 handler:^(NSError * error){
[doc closeWithCompletionHandler:nil];
}];
}}
有关编写异步操作方法的更多详细信息,请参见XCTestExpectation参考文档。
写作性能测试
性能测试需要一段代码,你要评估它并运行它十次,收集平均执行时间和运行的标准偏差。这些单独测量的平均值形成测试运行的值,然后可以将其与基线进行比较以评估成功或失败。
注意: 基线是你指定用于评估测试通过或失败的值。报表UI提供了设置或更改基准值的机制
要实现性能测量测试,你需要在Xcode 6和更高版本中使用XCTest中的新API编写方法。
- (void)testPerformanceExample {
//这是一个性能测试用例的例子。
[self measureBlock:^ {
//放你想要测量的代码这里的时间。
}];
}}
以下简单示例显示使用计算器示例应用程序写入测试添加速度的性能测试。A measureBlock:与XCTest的时间迭代一起添加。
- (void)testAdditionPerformance {
[self measureBlock:^ {
//设置初始状态
[calcViewController press:[calcView viewWithTag:6]]; // 6
//迭代100000个循环的添加2
for(int i = 0; i <100000; i ++){
[calcViewController press:[calcView viewWithTag:13]]; // +
[calcViewController press:[calcView viewWithTag:2]]; // 2
[calcViewController press:[calcView viewWithTag:12]]; // =
}}
}];
}}
性能测试一旦运行,就会在查看实施文件时在源编辑器中,在测试导航器中以及在报告导航器中提供信息。单击信息将显示各个运行值。结果显示包括将结果设置为测试的未来运行的基线的控制。基准按每个设备配置存储,因此你可以在几个不同的设备上执行相同的测试,并且每个设备根据特定配置的处理器速度,内存等保持不同的基线。
注意: 性能测量测试始终在第一次运行时报告故障,直到在特定设备配置上设置基准值。
有关性能测量测试的编写方法的更多详细信息,请参见XCTestCase参考文档。
编写UI测试
使用XCTest创建UI测试是与创建单元测试相同的编程模型的扩展。
总体上使用类似的操作和编程方法。
工作流和实现的差异集中在使用UI记录和XCTest UI测试API,在用户界面测试中描述。
使用Swift编写测试Swift访问控制模型(如Swift编程语言(Swift 3.1)的访问控制部分所述)阻止外部实体访问在应用程序或框架中声明为内部的任何东西。默认情况下,为了能够从测试代码访问这些项目,你需要将他们的访问级别提升为至少公开,从而降低Swift的类型安全性的好处。
Xcode为这个问题提供了一个两部分的解决方案:
1.当你将Enable Testability构建设置设置为时Yes,默认情况下对于新项目中的测试构建为true,Xcode -enable-testing在编译期间包含标志。这使得编译模块中声明的Swift实体有资格获得更高级别的访问。
2.将@testable属性添加到在启用了测试的情况下编译的模块的导入语句中时,将激活该范围中该模块的提升式访问。标记为内部或公共的类和类成员表现为好像被标记为打开。被标记为内部的其他实体好像被宣布为公开。
请注意,你不以任何方式更改源代码。你只修改编译(通过抛出一个标志)和测试代码(通过修改import语句)。例如,对于AppDelegate名为“MySwiftApp”的应用程序,请考虑像此实现一样的Swift模块。
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
@IBOutlet weak var window: NSWindow!
func foo() {
println("Hello, World!")
}
}
要编写允许访问类的测试类,AppDelegate请使用import属性修改测试代码中的@testable语句,如下所示:
// 导入基于XCTestCase的XCTest
import XCTest
// 导入基于NSApplication的AppKit
import AppKit
//导入基于AppDelegate的 MySwiftApp
@testable import MySwiftApp
class MySwiftAppTests: XCTestCase {
func testExample() {
let appDelegate = NSApplication.sharedApplication().delegate as! AppDelegate
appDelegate.foo()
}
}
有了这个解决方案,你的Swift应用程序代码的内部函数可以完全访问你的测试类和方法。为@testable入口授予的访问权限确保其他非测试客户端不违反Swift的访问控制规则,即使在编译测试时。此外,因为你的发布版本不支持可测试性,所以你的模块的常规用户(例如,如果你分发框架)无法通过这种方式访问内部实体。
注意: @testable仅提供内部功能的访问; 文件私有和私有声明在使用时在其通常范围之外是不可见的@testable.
XCTest断言
你的测试方法使用XCTest框架提供的断言来呈现Xcode显示的测试结果。所有断言具有类似的形式:要比较的项目或逻辑表达式,失败结果字符串格式,以及要插入字符串格式的参数。
注意: 所有断言的最后一个参数是format...一个格式字符串及其变量参数列表。XCTest为所有断言提供一个默认的失败结果字符串,使用传递给断言的参数进行汇编。该format字符串提供了除了提供的描述之外还可以选择提供的故障的附加的自定义描述的能力。此参数是可选的,可以省略。
例如,查看快速入门中提供的testAddition方法中的此断言:
XCTAssertEqualObjects([calcViewController.displayField stringValue], @"8", @"Part 1 failed.");
阅读这个简单的语言,它说“当从控制器的显示字段的值创建的字符串不同于参考字符串'8'时,指示失败。” Xcode信号与测试导航器中的失败指示器如果断言失败,Xcode还会在问题导航器,源代码编辑器和其他位置显示带有描述字符串的故障。源编辑器中的典型结果如下所示:
测试方法可以包括多个断言。如果任何断言包含报告失败,Xcode会发出测试方法失败的信号。
断言分为五类:
无条件失败。使用这个,当简单地到达特定分支的代码表示失败。这个类别中唯一的断言是XCTFail。
平等测试。使用这些来断言两个项目之间的关系。例如,XCTAssertEqual断言两个表达式具有相同的值,而XCTAssertEqualWithAccuracy断言两个表达式在一定精度内具有相同的值。这个类别还包括不平等的测试,例如XCTAssertNotEqual和XCTAssertGreaterThan。
布尔检验。使用这些来断言布尔表达式以某种方式计算,例如使用XCTAssertTrue或XCTAssertFalse。
无测试。使用这些来断言项目是或不是nil,例如使用XCTAssertNil或XCTAssertNotNil。
异常测试。使用这些来断言评估表达式是否生成异常。你寻找任何异常抛出XCTAssertThrows,或者你可以寻找一个具有断言的特定异常XCTAssertThrowsSpecific。你还可以断言反向,当评估表达式时,使用类似的函数没有抛出异常XCTAssertNoThrow。
该XCTest框架参考包含了所有可用的断言功能的完整枚举。
使用Objective-C和Swift的断言
当使用XCTest断言时,你应该知道当编写Objective-C(和其他基于C语言)代码和编写Swift代码时,断言的兼容性和行为的根本区别。了解这些差异使得编写和调试测试更容易
执行等式测试的XCTest断言分为比较对象和比较非对象的那些。例如,XCTAssertEqualObjects测试两个解析为对象类型的XCTAssertEqual
表达式之间的相等性,同时测试两个解析为标量类型值的表达式之间的相等性。通过在描述中包括“此测试是针对标量”,在XCTest断言列表中标记此差异。使用“标量”标记断言通知你基本区别,但它不是对哪些表达式类型是兼容的确切描述。
对于Objective-C中,标记为标量类型断言可与能与相等比较运营商使用的类型可以使用:==,!=,<=,<,>=,和>。如果表达式解析为使用这些运算符的任何C类型,结构或数组比较,它被认为是一个标量。
对于Swift,标记为标量的断言可以用于比较符合Equatable协议的任何表达式类型(对于所有“等于”和“不等于”的断言)和Comparable协议(对于“大于”和“小于”的断言)。此外,标标量断言有覆盖的[T]和[K:V],在那里T,K和V符合Equatable或Comparable协议。例如,等价类型的数组与其XCTAssertEqual(::_:file:line:)键和值都是可比较类型的字典兼容XCTAssertLessThan(::_:file:line:)。
注意: 在Swift中,NSObject符合Equatable,因此使用XCTAssertEqualObjects工程,但不是必需的。
在你的测试中使用XCTest断言也不同于Objective-C和Swift,因为语言在处理数据类型和隐式转换方面有所不同。
对于Objective-C,在XCTest实现中使用隐式转换允许比较操作独立于表达式的数据类型,并且不检查输入数据类型。
对于Swift,不允许隐式转换,因为Swift对类型安全更严格; 两个参数的比较必须是相同的类型。类型不匹配在编译时和源编辑器中标记。