iOS 中的测试是有独立的 Test Target,这意味着独立的命名空间,通常你只能从 Test 中访问类中 public 部分,但从 Xcode 7 开始,增加了 @testable,使得可以访问类内部的一些东东,注意并不包括 private
实际使用时,在 Test Class 的起始位置 @testable import xxxClass
Test Target 是由许多 Tests Class 组成,每个 Test class 都是 XCTest
的子类,并且有很多测试方法,每个 test 方法测试单一的功能,并且以 test 关键字开头
test class 通常包含这几个方法
Assertions
你可以在实际测试中使用 XCTAssert 方法,他通常需要一个 Boolen 参数,如果为 true,测试通过,false 测试失败,iOS 提供了相当多的 assert 方法来供你使用
XCTAssert XCTAssertEqual XCTAssertEqualWithAccuracy XCTAssertFalse XCTAssertGreaterThan XCTAssertGreaterThanOrEqual XCTAssertLessThan XCTAssertLessThanOrEqual XCTAssertNil XCTAssertNotEqual XCTAssertNotEqualWithAccuracy XCTAssertNotNil XCTAssertTrue XCTFail
通常程序都是顺序执行的,如果存在异步调用 Asynchronous call
,即:如果有一个 completion handle
要等待异步结束后才会被执行,那么可能整个 test 都执行完毕了,你的 completion handle
还没有被调用,为了解决这一问题 test framework 提出了 expectation 的概念,你可以设置一个 expectaion,并给以描述,稍后等待这个 expectation 被满足,在这段时间可以调用 asynchronous,这个 expectation 会等待 直到被满足,为了防止无限等待下去,你可以提供一个超时时间
Swift 创建一个 Mock Object 要比 OC 稍难一些,(OC 的动态分发机制),不过 Swift 可以通过我们之前提到的 Subclass 方式来创建(第一部分有提到过)
性能测试主要是测试 block 执行的时间,可将要测试代码放到 block 中
measureBlock() { xxx }
code coverage主要是让你知道你代码哪部分被测试过了 开启 Gather coverage data
UI test 主要和 proxy classes 进行交流,后者作为 app 的基本元素,代表了实际的交互元素(正如其名并不是真正的)有如下类型的 proxy class :
这些实例有一堆方法,例如:输入文字,点按,滑动等操作。你可以调用这些方法来执行这些动作,从而达到测试 UI action 的目的。这些 UI 元素还有一些属性,例如 frame
, title
, enabled
, exists
等,你可以添加 assertions 在这些属性上,来确保这些 UI 正常工作
这些 UI 元素最后还可以向你汇报他们的 子元素
,你可以使用 descendants 匹配相关类型得到一个 UI 元素的所有后代树,也可以通过 children 类型匹配他的直系子元素。
类型是你所要查询的元素,比如 navigation bar
或 static text
。比如你可以调用 descendants 匹配 table 类型的方法来找出 app 中所有的 table 元素。当然除了 table,你还可以匹配很多类型的 UI 元素
import XCTest @testable import StackReview class StackReviewUITests: XCTestCase { let app = XCUIApplication() override func setUp() { super.setUp() // Put setup code here. This method is called before the invocation of // each test method in the class. // In UI tests it is usually best to stop immediately when a failure occurs. continueAfterFailure = false /* UI tests must launch the application that they test. Doing this in setup will make sure it happens for each test method. */ XCUIApplication().launch() /* In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. */ } override func tearDown() { // Put teardown code here. This method is called after the invocation of // each test method in the class. super.tearDown() } func testAbout() { let startTitleLable = app.navigationBars.staticTexts["StackReview"] XCTAssertTrue(startTitleLable.exists, "Should be on the start screen") app.buttons["About"].tap() let aboutTitleLabel = app.navigationBars.staticTexts["About"] XCTAssertTrue(aboutTitleLabel.exists, "Should be on the about screen") } func testTableViewTap() { let tableText = app.tables.staticTexts["Stack 'em High"] let pancakeText = app.staticTexts["Stack 'em High"] let hideDetailsButton = app.buttons["Hide Details"] tableText.tap() XCTAssertTrue(pancakeText.exists) XCTAssertTrue(hideDetailsButton.exists) } }
let app = XCUIApplication()
得到你要测试的 App 代理 testAbout()
方法测试了 navigationBars 上的标题 StackReview 是否存在,这里通过 app.navigationBars.staticTexts["StackReview"]
找到对应的 label 元素(XCUIElement 类型),之后通过 assert 判断是否真正存在。这里还演示了 About 按钮的点击操作 app.buttons["About"].tap()
testTableViewTap()
测试了点击 cell 导航到另一个 VC 这里都通过 staticTexts["title"](元素标题的方式来查找对应的元素)
上面我们介绍了使用 XCUIElement 来代表你的界面元素,为了得到这些代理元素,你已经使用了 XCUIElementQuery
,他会帮助你搜索整个 view 层级树,找到你感兴趣的元素。
Element queries 这种情形下类似于集合,你可以使用集合的一些常用方法
从 Xcode 7 现在可以将你与 UI 的交互操作录制下来,然后为你自动生成这些交互 code。即你只需要在模拟器上指指点点, Xcode 就会自动替你生成这些 element queries code 了。但是 Xcode 不会写 assertion 语句,具体的测试还是要靠你自己完成。
实际操作也很简单,首先写一个 empty test method,将光标移到方法内,点击下面的小红点(Record UI Test)这个时候模拟器会弹出来,你随便做点操作,Xcode 会替你生成一些代码:
生成的代码,你可以微调,主要那个向下的箭头
最后你可以根据自己的需要插入一些 assert 判断。在实际应用中 Recording UI Test 并不是那么顺畅,感觉还有很大的改进空间。