Xcode测试
前言
总算在今天把单元测试的官方文档翻译写成了一片博客。首先感谢党,感谢人民,感谢我的父母。也必须感谢下我们部门的领导给了我一个这样的时间来做这件事情。翻译的不算太好,很多地方都还只是一个概念,在翻译的时候存在一些困难。而且单元测试也不止于翻译了这篇官方的文档,它应该是一系列的文档和说明。我会在今年的时间里,努力去写出来一个雏形。希望更多的人可以通过这些介绍认识到测试。
介绍
使用Xcode测试
Xcode提供的一个功能,可以进行广泛的软件测试。主要测试你的项目的健壮性,减少BUG,让你的产品更快的分发和销售。按照预期的功能测试,提高用户的满意度。测试可以帮助你更快开发、少做无用功。
目录结构
在这份文件中,你将学习如何使用Xcode包含的测试功能。XCTest框架添加测试的时候自动链接上去的。
- 1.快速启动,在Xcode5引进XCTest框架开始,配置项目测试的过程已经简化可以在导航栏自动的测试和运行。
- 2.性能测试,Xcode6以及更高的版本让你能够跟踪和衡量基本单位的性能变化。
- 3.UI测试,Xcode7增加了对用户界面的测试能力。
- 4.持续集成和Xcode服务器,Xcode测试可以使用命令脚本来执行或配置在Mac上的漫游器自动运行Xcode服务器上执行。
- 5.现代化,Xcode中包含转换一测试的OCUnit测试项目迁移。
前提
你应该熟悉应用程序的设计和编程的概念。
快速启动
快速启动其实就是介绍了测试的组成部分你可以对测试有一个简单的理解。
介绍测试的导航栏
当你准备做测试工作的时候,你会经常使用测试的导航栏。
测试的导航栏目的是缓解你测试代码的编写、管理、运行、审查测试的操作。你可以点击导航栏中的图标,位于show the issue导航和show the debug导航之间。当你创建了一些测试的项目你可以在这里看到一个导航视图。
图中的测试导航现实测试包、类和包含在测试项目方法的分层列表。图中的项目是一个简单的计算器项目。计算器使用一个框架实现,你可以在最高层的SampleCalcTests测试包里看到测试的代码。
注意:Xcode的测试目标产生后显示在测试导航里。如果你的测试使用数据文件、图像等等,他们可以被添加到所述的测试包,并且可以在运行的时候访问一个NSBundle的API。使用+[NSBundle bundleForClass:]确定你获得正确的包来进行测试。Xcode的方案控制构建了什么。方案还控制哪个可用的测试方法用于测试操作需要去执行。你可以启用和控制通过点击该项目在测试导航的列表中,选择启用或者从快捷菜单中禁用。从而启用或禁用测试包、类和方法。
此视图中的主要测试包是SampleCalcTests。SampleCalcTests包括一个测试类,后面包含九个测试方法。运行按钮在测试项目名称的右侧。这是一个非常方便的方法,所有的测试项目都集中到了这里(如果使用了一些第三方的测试可能有一些不会出现在这里列表当中)。运行以后红色的X代表测试不通过,绿色的对勾代表测试通过。运行的测试项目是使用断言来检测通过或者故障。
单击列表中的任何测试类或者测试方法打开测试类。测试类和测试方法在gutter都会显示一个可点击的测试的标志(可进行单独的测试)。
在测试导航的底部你可以添加一个测试,也可以通过一些字符串缩小你的查找范围。
为你的App添加测试
在Xcode5或者更高的版本你创建应用的时候可以通过勾选测试的按钮来为项目添加一个测试。当你创建好以后你可以在你的项目里看到一个测试包、测试和模版测试的方法。但是你的项目可能是由比较早的Xcode版本生成的。这里介绍的就是当你项目里预先没有测试的时候的一些操作。
创建一个测试Target
打开测试导航,单击左下角的加号按钮,new unit test target。
在弹出框中选择OS X或者iOS单元测试,然后单击下一步,设置相关的信息。
点击finish,完成以后点开。包含一个模版测试类和两个测试方法的模版。
运行测试,查看结果
现在你为你的项目添加了一个测试,然后去开发测试一些有意义的东西。首先按住鼠标指针移动到SampleCalcTests在测试导航测试类,然后点击运行按钮运行该类中的所有测试方法。结果在方法名称的右边,编辑区方法名的左边。
因为模块测试和性能测试都是空的,所以测试成功了。并没有失败的断言。measureBlock:处单击左侧的“钻石”会显示性能测试的结果。
这个面板允许设置性能基线以及编辑基线和最大STDDREV参数。
编辑测试和重新运行
这个实例项目是一个计算器应用程序,你需要检查加、减、乘、除算法的正确性,以及测试其他计算器功能的操作。测试是在项目中添加和编写的,你可以测试任何你想要的测试。(这就要求你有一个很好的代码结构来提供测试)
eg:你可以在SampleCalcTests.m中添加实例声明和引入类。
import
-
//
-
// Import the application specific header files
-
import "CalcViewController.h"
-
import "CalcAppDelegate.h"
-
@interface CalcTests : XCTestCase {
-
// add instance variables to the CalcTests class
-
@private
- NSApplication *app;
- CalcAppDelegate *appDelegate;
- CalcViewController *calcViewController;
- NSView *calcView;
-
}
-
*@end
然后给测试方法的起一个描述性的名称,如testAddition并添加要实施的方法。
*- (void) testAddition
-
*{
-
*//obtain the app variables for test access
-
*App = [NSApplication sharedApplication];
-
calcViewController = (CalcViewController)[[NSApplicationsharedApplication] delegate];
-
*calcView = calcViewController.view;
-
*//perform two addition tests
-
*[calcViewController press:[calcView viewWithTag: 6]]; // 6
-
*[calcViewController press:[calcView viewWithTag:13]]; // +
-
*[calcViewController press:[calcView viewWithTag: 2]]; // 2
-
*[calcViewController press:[calcView viewWithTag:12]]; // =
-
*XCTAssertEqualObjects([calcViewController.displayField stringValue],@"8", @"Part 1 failed.");
-
*[calcViewController press:[calcView viewWithTag:13]]; // +
-
*[calcViewController press:[calcView viewWithTag: 2]]; // 2
-
*[calcViewController press:[calcView viewWithTag:12]]; // =
-
*XCTAssertEqualObjects([calcViewController.displayField stringValue],@"10", @"Part 2 failed.");
-
}
如果你改变了测试的方法,测试导航列表也会做相应的改变。
编辑完以后可以使用测试导航(或者gutter)来运行testAddition方法。
当一个断言失败,在测试导航和gutter会突出显示。根据提示信息你可以非常轻易的找到这个错误。进行修改之后就能够运行成功。
使用setUp()和tearDown()方法
Xcode运行一个测试包的时候会走这个测试类中的所有方法。那这个测试类中如果都需要一个初始化对象,你就要在每一个方法中对这个对象进行初始化,这样就会造成很多重复的代码。但是XCTest框架提供了两个实例方法用于测试类的初始化和释放。你可以用这两个实例方法将这些共同调用的方法写进去。
使用setup和teardown方法非常简单。
*- (void)setup
-
{
- [super setUp];
- *// Put setup code here. This method iscalled before the invocation of each test method in the class.
- *// obtain the app variables for test access
- *app = [NSApplication sharedApplication];
- calcViewController = (CalcViewController)[[NSApplicationsharedApplication] delegate];
- *calcView = calcViewController.view;
-
}
Setup方法会在测试方法之前调用,所以测试方法可以直接试用初始化过的属性。
概要
从这个简短的快速入门可以看出来,为一个项目添加测试是很简单的。但是还是有一些注意事项:
- Xcode中设置了大部分的基本测试配置。当你添加一个新的测试的时候,Xcode会自动为你添加这些测试方法。使用一个单独的方法进行测试,可以在测试导航中找到。
- 测试导航可以让你轻松的找到编辑测试的方法,你可以运行,在导航测试或者gutter。测试失败的时候你可以在gutter中看到失败的标志。
- 单个测试方法可以包括多个断言,从而导致单一的合格或者不合格的测试结果。你可以根据需求来做简单或者复杂的测试。setup和teardown实例方法可以让你将通用的代码写在里边。
基础测试
测试是指检验你的应用程序代码和库代码能否成功运行的过程,用于衡量预期结果。通过执行一些操作,测试可在执行一些操作后检查一个对象的实例变量的状态,以确定你的代码在受到边界条件变化时是否会抛出一个特定的异常等。
定义测试范围
所有的软件都是通过很多的单元组合起来的,也就是说,小的组件合在一起形成较大的、功能更强的高级组件,直到符合项目的需求。良好的测试需要涵盖该组合的所有功能。其中单元测试通常处理该项目功能级的小组件。而XCTest允许你为任何层次结构的各个级别的组件编写相应的测试。
测试组件由什么构成完全取决于你自己,可以是一个类中的一个方法,也可以是完成一个基本目的的一组方法。例如,一个算术运算,参见苹果的官方demo。处理tableView的内容间和代码数据结构中持有列表名称间的交互有不同的方法。每个方法的操作都意味着应用程序功能的组成部分对它的测试。一个测试组件的行为应该是完全确定的,无论测试成功或者失败。
把你的应用程序的行为划分为越多的组件,就越能有效的测试你的代码能否满足参考标准的各种细节,尤其是当项目不断的迭代更新的时候。对于很多组件组成的大型项目来说,你需要运行很多的测试来彻底检测整个项目。如果可能的话,测试应该快速运行,担忧一些测试确实很大,所以运行可能会慢一点。当有一些故障出现的时候,可以运行一些小的测试快速测试,这样可以轻松诊断和修复问题。
为项目组件设计测试是测试驱动开发(test-driven-development TDD)的基础,也是一种编写代码测试之前编写测试逻辑的编码方式。这种开发方法可以让你在实施之前确定代码需求和边界情况。编写测试后,开发旨在通过测试的算法。在代码通过测试以后,你才有了提高代码的基础,才有信心在下一次运行这些测试时能鉴定一些预期行为(导致你的产品产生BUG)的变化。
甚至在你不使用测试驱动开发时,测试还可以降低修改代码引入的BUG数量,从而帮你提高代码的特性和功能。在一款运行的应用程序中进行测试以确保未来的更改不会改变应用的行为。当你修复这些BUG后,你需要添加测试以确保该BUG已经被修复。测试还可以检测你的代码,所以有成功和失败两种预期,以覆盖所有边界条件。
注意:为一个没有考虑到测试的项目添加测试可能会要求重构部分代码来使测试变得更加简单。“Writing Testable Code”包含游泳的编写可测试代码的简单指南。
组件可以包括你的应用程序的各部分之间的交互。由于有些测试可能需要很长时间,所以你可能希望定期或者只在一台服务器上运行他们。(你可以组织自己的测试并以多种方式运行它们,以满足不同的需求)。
性能测试
测试的组件可以同时测试功能和记录性能。使用XCTest提供的API你可以测量以时间为基准的性能测试,对一个相似的方法或者功能你可以跟踪性能的提高或者退化。
为了衡量一个性能测试的成功或者失败,测试必须有一个基准用来评估。基线是运行10次以后计算出来的平均时间的性能测试。如果与基线相差太多的时候为失败。
**注意:当你第一次运行性能测试的时候系统会提示失败,因为第一次运行的时候基线是未知的。一旦你设定了一个测量准线,XCTest会报告成功或者失败,并提示相信的结果信息。**
用户界面测试
功能测试和性能测试一般都被称为单元测试,其中的单元是相对于组件和最小的模块来确定的(大概意思)。单元测试主要是让相关的组件能够按照预期的那样交互。从设计的角度来看,单元测试是在开发的时候编写的满足你的预期。
用户通过源代码进行界面的交互。界面的交互一般是整合一整个子系统的操作来实现预期的功能。这些很难使用单元测试来进行测试,这种特殊的测试被称为UI测试。
UI测试更贴近于用户的体验。你可以编写模拟事件添加到UI测试中,捕捉这些对象的反应,然后测试正确性和性能和单元测试十分类似。
应用程序测试和库测试
Xcode测试提供两种测试:应用程序测试和库测试。
应用程序测试。应用程序测试可以检查App的代码组件,例如苹果测试的官方demo。你可以利用应用程序来确保你的UI控件保持原有的位置,并且你的控件和控制器对象能够和对象模型正确地工作。
库测试。库测试可检查独立代码(不再应用程序中运行的代码)的行为是否正确。利用库测试,你可以将整个库的组件放在一起,通常是测试库的对象和方法。你也可以使用库测试来执行代码的压力测试,以确保它在极端的情况下也能正确执行(不大可能出现在一个运行的app中)。这些测试可以帮助你生成一个“健壮的”代码,即使在没有预料的情况下也能运行正常。
XCTest-Xcode测试框架
XCTest是一个Xcode5及以上版本中使用的测试框架。如果你以前使用过Xcode OCUnit测试,你可能会发现XCTest和OCUnit有些相似。XCTest是OCUnit更现代化的替代,可以更好的与Xcode集成,为将来Xcode测试功能的改进奠定了基础。Xcode把XCTest.framework并入你的项目,而不是SenTesting.framework。该框架提供可以让你设计测试并在代码中运行的API。
**注意:Xcode包括一个迁移助手,可用来转换包含OCUnit测试的项目。更多OCUnit想XCTest转换的详细信息,请参阅“Transitioning frome OCUnit to XCTest”**
测试从哪里运行
当你开始创建测试的时候,记住下面的方法:
- 专注于测试你的代码的最基础的功能、模型类、方法,它们与控制器相交互。
应用程序的高级框图很可能会包含Model、View和Controller类别,对于每个使用Cocoa和Cocoa Touch的开发者来说,这是一个熟悉设计模式。当你编写要覆盖所有Model类别的测试时,你必须要知道App的基础已经进行了良好的测试,而且要编写Controller Classes测试之前,它将带你接触你的应用程序更复杂的部分。 - 作为一个可选的起始点,如果你正在编写一个框架或库,你可能想从App接口开始。从那里你可以按照你的方式进入内部类。
编写测试类与方法
但你使用测试导航面板往项目中添加测试target的时候,Xcode会在测试导航面板里展示除测试类和测试方法。在测试target里是包含测试方法的测试类。
测试target、测试包、测试导航
在学习创建测试类前,有必要看看测试导航面板。它对创建和运行测试工作极为重要。
测试导航面板罗列了测试包里的所有组件内容,并在一个层次列表里展示出测试类和测试方法。下边是一个工程的测试导航面板视图,包含了多个测试目标,展示了测试包、测试类、以及测试方法的嵌套层级。
测试包里可以包含多个测试类。你可以使用测试类把测试分到相关的组群里,或者按照功能分,或者按照组织目的分。例如苹果测试的官方demo,创建了BasicFunctionsTests、AdvancedFunctionsTests、DisplayTests classes三个类,但它们都属于Mac_Calc_Tests测试包。
一些测试类型可能会共享某些类型的setup和teardown,把这些测试整合到类里边会更加合理,这样可以最小化每个测试方法所需的编写代码。
创建测试
可以使用加号按钮在导航测试面板中选中“New Test Class”命令来创建新测试类。
基于你在配置页面键入的测试类名,你添加的每一个类都会使得一个名为TestClassName.m的文件被添加到项目中。
注意:所有的测试类都是XCTest框架XCTestCase类的子类。
尽管Xcode会默认的把测试类添加到工程测试项目所创建的组中,你还是可以在项目中组织自己选择的文件。当你按下Next按钮,标准的Xcode添加文件页面如下所示:
你可以使用相同的方法在工程导航面板中添加文件。具体使用当法详见“Adding an Existing File or Folder”。
注意:当你用Xcode5及以上版本创建工程时,一个测试target和相关的测试包都会被默认的创建,名称根据工程名获得。比如创建名为MyApp的项目,则会自动生成一个名为MyAppTests的测试包,以及一个名为MyAppTests的测试类,关联在MyAppTests.m实例文件中。
测试类的结构
测试类包含以下的基础结构:
*#import
*@interface SampleCalcTests : XCTestCase
*@end
*@implementation SampleCalcTests
*- (void)setUp {
*[super setUp];
*// Put setup code here. Thismethod is called before the invocation of each test method in the class.
*}
*- (void)tearDown {
*// Put teardown code here. Thismethod is called after the invocation of each test method in the class.
*[super tearDown];
*}
*- (void)testExample {
*// This is an example of afunctional test case.
*// Use XCTAssert and relatedfunctions to verify your tests produce the correct results.
*}
*- (void)testPerformanceExample {
*// This is an example of aperformance test case.
*[self measureBlock:^{
*// Put the code you want tomeasure the time of here.
*}];
*}
@end
测试类用Objective-C实现。注意实现里包含了方法,比如setup和teardown的基本实例方法。这些方法是必须的。如果类中的所有的测试方法都需要相同的代码,你可以定制setup和teardown来包含这些代码。你添加的代码在每一个测试方法的之前和之后调用。你可以选择添加定制的设置(+ (void)setUp)和卸载teardown(+ (void)tearDown)方法,它们在类里所有的测试方法的之前和之后调用。Xcode执行测试可以明确这些方法的使用。
测试执行的流程
在执行测试的过程中,XCTest找到所有集成与XCTestCase(它是测试类)的类,为每一个类运行它们的测试方法。
对于每个类来说,测试开始于运行类的setup方法。对于每个测试方法来说,一个新的类实例被创建,他的实例setup方法就会执行。在跑完测试方法之后,实例teardown方法。类中这样连续重复执行所有的测试方法。在运行了teardown最后的测试方法后,Xcode会执行类teardown方法,并开始下一个类。这种序列已知重复直到跑完所有测试类的所有测试方法。
编写测试方法
你通过编写测试方法把测试写到测试类中,一个测试方法是以test开头的测试类的实例方法,没有参数,返回void,比如TestColorsRed测试方法调用工程中的代码,如果代码没有产生预期的效果,那么会用一系列的断言API报错。比如,一个函数返回值可能于预期相比不同,或者你的测试方法使用了某个不适当的方法都将会抛出异常。“XCTest Assertions”描述了这些情况。
为了是测试方法能够正常访问被测代码,引入正确的头文件到测试类中很重要。
当Xcode运行测试时,它调用的每个测试方法都是独立的。因此每个方法需要准备和清理辅助变量、结构以及与主题API进行交互的对象等。如果类中所有的测试方法的代码是相同的,你可以直接把它添加到必走的setup和teardown的实例方法中,详见“Test Class Structure”。
下边是一个测试方法的模型:
*- (void)testColorIsRed {
- *// Set up, call test subject API. (Codecould be shared in setUp method.)
- // Test logic and values, assertions reportpass/fail to testing framework.
- // Tear down. (Code could be shared intearDown method.
}
这里有一个简单的测试方法例子,检查是否成功为SampleCalc创建了CalcView实例,详见“快速启动”。
*- (void) testCalcView {
- // setup
- app = [NSApplication sharedApplication];
- calcViewController =(CalcViewController*)[NSApplication sharedApplication] delegate];
- calcView = calcViewController.view;
- XCTAssertNotNil(calcView, @"Cannot findCalcView instance");
- // no teardown needed
}
编写异步操作测试
测试是同步的,因为只有当上一次测试结束以后才进行下一个测试。但是越来越多的代码使用异步执行。为了处理这些异步调用执行的方法和功能组件,Xcode6增强了包括序列化异步执行测试方法的能力,等待异步回调或者超时完成。
例如:
*// Test that the documentis opened. Because opening is asynchronous,
*// use XCTestCase'sasynchronous APIs to wait until the document has
// finished opening.
-(void)testDocumentOpening
{ *// Create anexpectation object.
- *// This test only has one, but it'spossible to wait on multiple expectations.
- *XCTestExpectation *documentOpenExpectation = [selfexpectationWithDescription:@"document open"];
- *NSURL *URL =[[NSBundle bundleForClass:[self class]]
URLForResource:@"TestDocument"withExtension:@"mydoc"];
- UIDocumentdoc = [[UIDocument alloc] initWithFileURL:URL];
- *[docopenWithCompletionHandler:^(BOOL success) {
*XCTAssert(success);
*// Possibly assert other things here about the document after it hasopened...
*// Fulfill the expectation-this will cause -waitForExpectation
*// to invoke its completion handler andthen return.
*[documentOpenExpectation fulfill];
- *}];
- *// The testwill pause here, running the run loop, until the timeout is hit
- *// or all expectations are fulfilled.
- *[selfwaitForExpectationsWithTimeout:1 handler:^(NSError *error) {
*[doc closeWithCompletionHandler:nil];
- *}];
*}
有关更多异步操作的相信信息,请参阅“XCTest.framework”下的XCTestCase+AsynchronousTesting.h文件。
性能测试
性能测试就是在代码块中运行10次,收集平均执行时间和和标准的偏差。这些值形成一个平均值,用来评估和基线对比的成功或者失败。
**注意:基线是您指定要用于检测合格与否的瓶价值。该报告的UI提供了一种机制来设置或者更改基准值。**
为了实现性能测试,在Xcode6以后当中使用新的XCTest方法的API。
*- (void)testPerformanceExample{
- *// This is an example of a performance testcase.
- *[self measureBlock:^{
*// Put the code you want to measure thetime of here.
- }];
*}
下面的例子简单的演示了一个用计算器应用程序编写的性能测试的测试速度。一个measureBlock随着时间的增加XCTest迭代。
*- (void) testAdditionPerformance{
- *[self measureBlock:^{
*// set the initial state
*[calcViewController press:[calcViewviewWithTag: 6]]; // 6
*// iterate for 100000 cycles of adding2
*for (int i=0; i<100000; i++) {
*[calcViewController press:[calcViewviewWithTag:13]]; // +
*[calcViewController press:[calcViewviewWithTag: 2]]; // 2
*[calcViewController press:[calcViewviewWithTag:12]]; // =
*}
- *}];
}
运行一次性能测试,在测试导航面板的导航器中提供信息。点击该信息会显示运行出来的值。结果显示包括控制设置的结果作为准线和未来的测试做对比。基线存储了每个设备的配置,所以你相同的测试在不同的设备上执行,会有不同的准线。(取决于配置的处理速度,内存等等)
注意:性能测试总是在第一次运行,直到准线被设置在一个特定的设备配置中报告失败。
有关性能测试的更多详细的信息,请参阅XCTest.framework下的XCTestCase.h文件。
编写UI测试
创建XCTest UI测试是单元测试的一个扩展对于相同的编程模型。参见“User Interface Testing”。
编写swift测试
swift控制器和模型不能访问程序内部和框架内部声明。在Xcode6访问swift的内部功能,你需要进入公共的切入点来进行测试,降低了swift的安全特性。
Xcode7提供了这个问题的解决方案:
- swift内部访问时,测试通过通知编辑器生成进行访问。编译器通过一个新的控制-enable进行更改,Xcode在接受到这个通知的时候生成。这种行为是控的,对新的项目的默认设置来进行设置。他不需要改变源代码。
- 通过修改导入头文件的声明,来改变测试入口的可见性。使用新的@testable属性导入,改变你的测试代码,不需要修改应用代码。
*import Cocoa
*@NSApplicationMain
*class AppDelegate: NSObject, NSApplicationDelegate {
*@IBOutlet weak var window:NSWindow!
*func foo() {
*println("Hello,World!")
*}
}
编写一个测试类,允许访问Appdelegate类,修改导入的语句与你的测试代码使用@testable属性
*// Importing XCTestbecause of XCTestCase
*import XCTest
*// Importing AppKitbecause of NSApplication
*import AppKit
*// Importing MySwiftAppbecause of AppDelegate
*@testable importMySwiftApp
*class MySwiftAppTests:XCTestCase {
- *func testExample() {
let appDelegate =NSApplication.sharedApplication().delegate as! AppDelegate
appDelegate.foo()
- }
}
有了这个解决方案,你的swift内部代码能够完全的被测试方法访问到。
XCTest断言
你的测试方法使用XCTest框架提供给的断言来展示Xcode的测试结果。所有的断言都有一个类似的形式:项目比较或逻辑表达,一个失败的结果字符串格式,和插入到字符串格式中的参数。
**注意:所有的断言最后一个参数是format…,格式字符串和变量参数列表。XCTest为所有断言提供了默认的失败结果字符串,可以使用参数传递到断言里。Format字符串提供了可选的额外的失败自定义描述,就可以进一步选择提供的描述。这个参数是可选的,也可以完全忽略。**
例如:
XCTAssertEqualObjects([calcViewController.displayField stringValue], @"8", @"Part 1 failed.");
这段简单明了的描述是说:“Indicate a failure when a string created from the value of the controller’s display field is not the same as the reference string ‘8’”。如果断言失败那么Xcode在测试导航面板发出失败信号,然后Xcode会在issues导航面板、源码编辑器以及其他地方展示失败的描述。下面是源代码编辑器典型的断言结果:
测试方法可以包含多个断言,如果任何断言包含失败报告,那么Xcode都会发出测试失败信号。
断言氛围物种类型:无条件报错、等价测试、nil测试、布尔测试以及异常测试。
用类别区分断言
下面的区域列出了XCTest断言。你可以XCTestAssertions.h中获取更多断言的信息。
- 无条件报错
XCTFail生成一个无条件的报错
XCTFail(format...)
- 等价测试
XCTAssertEqualObjectes,当expresson1不等于expresson2时报错(或者一个对象为空,另一个对象不为空)
XCTAssertEqualObjects(expression1, expression2, format...)
XCTAssertNotEqualObjects. 当expression1等于expression2时报错。
XCTAssertNotEqualObjects(expression1, expression2, format...)
XCTAssertEqual. 当expression1不等于expression2时报错,这个测试用于C语言的标量。
XCTAssertEqual(expression1, expression2, format...)
XCTAssertNotEqual. 当expression1等于expression2时报错,这个测试用于C语言的标量。
XCTAssertNotEqual(expression1, expression2, format...)
XCTAssertEqualWithAccuracy. 当expression1和expression2之间的差别高于accuracy 将报错。这种测试适用于floats和doubles这些标量,两者之间的细微差异导致它们不完全相等,但是对所有的标量都有效。
XCTAssertEqualWithAccuracy(expression1, expression2, accuracy, format...)
XCTAssertNotEqualWithAccuracy. 当expression1和expression2之间的差别低于accuracy将产生失败。这种测试适用于floats和doubles这些标量,两者之间的细微差异导致它们不完全相等,但是对所有的标量都有效。
XCTAssertNotEqualWithAccuracy(expression1, expression2, accuracy, format...)
- Nil(空)测试
XCTAssertNil. 当expression参数非nil时报错。
XCTAssertNil(expression, format...)
XCTAssertNotNil. 当expression参数为nil时报错。
XCTAssertNotNil(expression, format...)
- Boolean测试
XCTAssertTrue. 当expression计算结果为false时报错。
XCTAssertTrue(expression, format...)
XCTAssert. 当expression计算结果为false时报错,与XCTAssertTrue同义。
XCTAssert(expression, format...)
XCTAssertFalse. 当expression计算结果为true报错。
XCTAssertFalse(expression, format...)
- 异常测试
XCTAssertThrows.当expression不抛出异常时报错。
XCTAssertThrows(expression, format...)
XCTAssertThrowsSpecific.当expression针对指定类不抛出异常时报错。
XCTAssertThrowsSpecific(expression, exception_class, format...)
XCTAssertThrowsSpecificNamed. 当expression针对特定类和特定名字不抛出异常时报错。对于AppKit框架或Foundation框架非常有用,抛出带有特定名字的NSException(NSInvalidArgumentException等等)。
XCTAssertThrowsSpecificNamed(expression, exception_class, exception_name, format...)
XCTAssertNoThrow. 当expression抛出异常时报错。
XCTAssertNoThrow(expression, format...)
XCTAssertNoThrowSpecific. 当expression针对指定类抛出异常时报错。任意其他异常都可以;也就是说它不会报错。
XCTAssertNoThrowSpecific(expression, exception_class, format...)
XCTAssertNoThrowSpecificNamed. 当expression针对特定类和特定名字抛出异常时报错。对于AppKit框架或Foundation框架非常有用,抛出带有特定名字的NSException(NSInvalidArgumentException等等)
XCTAssertNoThrowSpecificNamed(expression, exception_class, exception_name, format...)
运行测试并查看结果
使用Xcode测试导航面板,可以很容易的运行测试并查看结果。有另外几种运行测试的交互方法。Xcode运行测试取决于一个scheme中开启了哪些test target。测试导航面板让你无需使用scheme编辑器就能直接控制那些被包含、被开启或关闭的test target、类以及方法。
运行测试命令
测试导航面板提供了快捷的方法,以将运行测试作为编码流程的一部分。测试同样可以直接在源代码编辑器里运行或者使用product菜单运行。
-
使用测试导航面板
在测试导航面板中,将鼠标停在测试包、类或方法名上时,会出现一个“Run”按钮。你可以运行某个特定测试,也可以运行一个类中的测试或运行一个测试包中的所有测试,这取决于列表中的鼠标停留位置。
将鼠标悬停在测试包名上,并点击右边出现的运行按钮来测试一个包中的所有测试。
将鼠标悬停在类名上,并点击右边出现的运行按钮来测试一个类中的所有测试。
将鼠标巡艇在测试方法名上,并点击右侧出现的运行按钮来测试一个单独的测试。
- 使用源代码编辑器
当在源代码编辑器中打开一个测试类,每个测试方法名字旁的边栏会明显的指示器。在指示器上悬停鼠标,出现一个运行按钮。点击按钮运行该测试方法,指示器随后显示该测试成功或失败的状态。再次将鼠标悬停到指示器上可以再次显示运行按钮来重复测试。这种机制一次只运行一个测试。
注意:出现在@implementation旁边的指示器同样也适用于类,允许你运行类中所有的测试。 - 使用Product菜单
- Product菜单包括了从键盘直接运行测试的快捷命令。
- Product>Test运行当前激活的方案(scheme)。键盘快捷键是Conmmand-U。
- Product>Build for>Testing和Product>Perform Action>Test without Building。这两个命令可以用来构建测试并运行独立于其他的测试。这是简化构建和测试进程的快捷命令。对于修改了代码并在构建过程中检查警告或错误时,这是特别游泳的,或者如果你知道当前的构建是最新的,他也可以加速测试过程。键盘快捷方式分别是Shift-Command-U和Control-Command-U。
- Product>Perform Action>Test。当你编辑一个测试方法时,这个动态菜单选项展示当前光标所处的测试方法,并允许你使用键盘快捷键方式来运行测试。命令的名称取决于将要运行的测试,比如Product>Perform Action>Test testAddition。快捷键Control-Opention-Command-U。
注意:除了源代码编辑器,该命令还基于工程导航面板中的选择情况运行。当人和一个导航面板处于激活状态,源代码编辑器就是去了焦点,并且该命令将使用任何一个导航面板中的当前选择作为输入。在测试导航面板中,可以选择测试包、类、或方法。在工程导航面板中,可以选择测试类实现文件,例如CalcTests.m。 - Product>Perform Action>Test Again。当测试方法在调试/编辑代码中暴露出问题时,返回最后一个被执行的测试方法非常有用。像Product>Perform Action>Test命令,运行的测试名称在命令中出现,例如Product>Perform Action>Test Again testAddition。键盘的快捷方式Control-Option-Command-G。
显示测试结果
XCTest框架有几种不同的方式在Xcode中展示测试结果。下面的屏幕截图展示了在哪里查看这些测试结果.
-
在一个或一组测试运行后,你可以在测试导航面板中浏览测试通过或者失败.
如果测试方法被叠加到各自的类中,或测试方法被折叠到测试包中,该标识则反映了封闭测试的集合状态,表明在这个类或者包中至少有一个测试方法不通过。
-
在源代码编辑器中,你可以浏览测试通过或者失败的标识和调试信息
- 在报告导航面板中,可以通过测试运行结果查看。
对于一个性能测试,单击时间列中的值,获得性能测试的详细报告。你可以通过单击各个测试运行按钮,查看10次运行的详细信息。编辑按钮可以修改基线和允许通过或者失败的最大标准偏差。
日志导航面板中,你可以浏览相关的错误描述和其他一些输出摘要。点击走遍的提示箭头标识,可以展开所有的测试运行细节。
注意:出了左边展开的三角图标,测试失败项右边的小图标可以展开显示更多信息。 -
调试控制台以文本形式展示了测试运行的综合信息。这些信息和日志导航面板中显示的信息一样。但如果你已经处于debug状态,那么debug的输出也会出现在这里。
使用Schemes(方案)和Test Target(测试目标)
Xcode Scheme控制着构建、运行、测试和调试这些菜单命令的行为。当创建Test Target并使用测试导航面板执行其他测试系统操作时,Xcode5为我们管理Scheme配置——例如,当你开启或者关闭一个测试方法、测试类或测试包时。使用Xcode Server和持续集成需要通过设置Manager Schemes页面中的复选框来将一个Scheme设置称共享,并将其连同你的工程和源代码导出到一个源文件仓库。
Scheme配置方法如下:
-
选择Scheme menu >Manage Scheme,弹出管理页。
这个工程中有两个Scheme,一个用来构建应用,一个用来构建框架/库。右侧的Share复选框用来将Scheme配置为共享,并允许Xcode Server bots使用。
- 在管理页中,双击一个Scheme,显示Scheme Editor。一个Scheme的测试行为标识了你执行测试命令时Xcode执行的测试。
注意:测试目标、测试类和测试方法所关联的测试导航面板和配置/设置助手通常维护所有关于测试操作的Scheme设置。
关于使用、配置和编辑Scheme的更多信息可以查看Scheme Configuration Help,或者视频WWDC 2012:Working with Scheme and Projects in Xcode。
构建测试-测试应用、测试库
应用程序在你的应用的上下文中运行,允许你创建测试,这些测试结合了不同的类、库/框架和应用功能的角度的行为。库测试独立于你的应用,在一个库或框架中采用实验类和方法,来验证它们的行为符合库的明确要求。
两种类型的测试包需要不同的构建设置。在新的target助手中通过选择目标参数来创建test target时,会执行构建设置配置。目标助手随着目标下拉菜单显示。名为SampleCalc的应用和名为CalcLibrary的库可供选择。
选择SampleCalc作为该测试目标所关联的构建产品配置构建设置来测试一个应用。测试的执行基于应用进程,测试在得到SampleCalc你可以根据自己的偏好改变它。
如果你选择CalcLibrary作为关联的构建产品,目标助手将会在配置构建设置来测试库。Xcode引导测试运行时上下文和框架/库,以及你的测试代码-基于一个Xcode管理的进程。这种情况下默认的产品名字来自于库目标(“CalcLibrary Tests”)。同样你可以基于自己的喜好更改。
默认构建设置
大多数情况下,配置构建设置来测试应用或库所要做的唯一一件事就是选择正确的测试目标。Xcode则自动关注构建设置管理。因为你可能有一个需要复杂构建设置的功能,所以了解Xcode为应用测试和库测试所建立的标准是非常有用的。
在这里使用官方的测试demo来演示:
-
在工程导航面板中单击SampleCalc进入工程编辑器,然后选择SampleCalc Tests应用测试目标。在编辑器的General面板中,有一个目标快捷菜单。该下拉菜单应当展示SampleCalc为目标。
你可以检查该构建设置是否正确。
-
点击构建设置,在搜索框中输入Bundle Loader。针对SampleCalc的应用测试被SampleCalc App加载。你可以看到针对Debug和Release的作为自定义参数的可执行文件路径。搜索Test Host也可以看到相同的路径。
该工程的库目标名为CalcLibrary,并且已经与名为CalcLibraryTests的测试目标关联。
-
选择CalcLibraryTests目标并查看General面板。CalcLibrary被作为目标。
-
像应用测试目标一样,你可以使用build Settings面板查看Bundle Loader和Test Host构建设置,下图展示Bundle Loader搜索设置。
在库测试目标的案例中,Bundle Loader和Test Host项目都没有关联的参数。这表明Xcode已经默认正确配置了它们。
测试的目的
应用测试在OS X,iOS(真机)和iOS模拟器上运行。库测试运行在OS X和iOS模拟器上。Xcode Server可以通过是党的配置源代码仓库,共享Scheme和bot在运行OS X Server的Mac上运行。更多关于Xcode Server运行目标的信息,请查看“Automating the Test Process with Continuous Integration”。
调试测试
所有标准的Xcode调试工具在执行测试的时候都可以使用。
测试调试工作流
要确定的第一件事情就是:造成测试失败的原因是测试中的代码有BUG还是执行的测试方法存在BUG。测试失败可以指出一些不同类型的问题,即有你的假设,在测试中的代码需求,也有测试代码本身,所以调试测试可以横跨几个不同的工作流。然而通常你的测试方法是相对较小和直接的,所以最好首先检查测试的目的是什么,以及它是如何实现的。
值得注意的一些常见的问题:
测试逻辑是否正确?实现是否正确?测试方法用作比较基础使用的字面值,检查它们的笔误和粗舞始终是个好主意。
假设是什么?例如,你可能在测试方法里使用了错误的数据类型,创建了一个你所测试的代码的范围错误。
-
是否使用了正确的断言来报告,“通过/失败”状态?例如,可能测试的状态需要XCTAssertTure而不是XCTAsserFalse。有时候很容易造成这些错误。
假设你的测试假设是正确的,并且测试方法也正确,那么错误一定在要测试的代码中。现在定位并修复它。
具体的测试调试工具
Xcode有几个工具专门用来在你测试时定位和调试代码。
-
测试失败断点
在断电导航面板中(BreakPoint Navigator),点击Add(加号)按钮,选择Add Test Failure breakpoint设置一个特殊的断点。
当测试方法触发了失败断言,这个断点会终止测试的运行。在测试代码发生错误点击后立马停止运行,可以让你快速的找到问题发生的位置。你可以看看testAddition测试方法,通过为错误字符串设置用来比较的参考标准,比较字符串被强行断言为失败。测试失败断点检测该失败断言并停止了该点测试的执行。
当测试终端,你也就停止了测试的执行。然后在断言前设置一个常规的断点,再次运行测试(为了简单期间,你可以在源代码编辑器侧栏中点击“Run”按钮来运行该测试),并继续调试操作来修复问题。 - 使用工程菜单命令运行测试
调试测试方法时,最好记住菜单命令Project>Perform Action>Test Again和Project>Perform Action>Test。如果你正在测试失败发生后编辑修复代码,或运行你正在编写的测试方法,它们提供了一个便捷的方式来测试最后一个方法。更多信息请查看“Using the Product menu”。
当然你也可以始终使用测试导航面板或源代码编辑器侧边栏中的“Run”按钮来运行测试,都很方便。 -
辅助编辑器类别
辅助编辑器分类中增加了两个专门的分类来运行特殊的测试。
- Test Callers category。如果你修复了一个饮用测试失败的方法,你可能会想要检查该方法在其他测试中是否被调用并运行成功。带着这个问题,在源代码编辑器中打开辅助编辑器,并从菜单中选择Test Classes分类。你可以从下拉菜单定位到任何调用它的测试方法的位置,这样你就可以运行它们,以确保你的修复没有造成其他的问题。
- Test Classes Category。该分类和Test Callers类似,不过显示的是包含测试方法的类列表,这些方法与主源码编辑器中编辑类的相关。对于增加测试来说,是一个非常好的机会,例如给还没有被并入测试方法中的新方法增加测试。
- 测试时的异常断点
通常当异常被异常断点捕获时就会终止测试执行,所以测试运行时通常都会关闭异常断点以避免不适当的定位抛出的断点。当你寻找一个特定的问题并想终止测试来修复它时可以打开异常断点。