什么是单元测试
单元测试:是对最小单元(方法、函数)的验证性测试。一般来说,单元测试不需要依赖外部环境、网络环境、数据库环境。
UI 测试:结合页面元素和交互流程,对应用业务进行测试。
单元测试原则
快速(Fast):测试应该能快速运行。
独立(Independent):测试应该相互独立,不能有依赖。
可重复(Repeatable):测试应当可在任何环境重复通过。包括无网络的环境。
自足验证(Self-Validating):测试应该有布尔值输出。
及时(Timely):测试应及时编写。单元测试应该恰好在使其通过的代码之前编写。如果业务代码更改,测试代码也应该考虑是否随其变动
哪些代码需要单元测试
模块 | 备注 |
---|---|
算法相关 | 优先最高,保证算法正确性,避免后期改动造成错误 |
对外提供的 Router | 保证外部调整和唤起正常 |
业务逻辑 | 自行创建测试数据和条件参数测试 |
网络数据处理 | 通过 Mock 处理测试 |
ViewController | 通过 UI 测试 |
ViewModel/xxxUtil/xxxManager | |
全局方法、类方法 | |
本地json/配置文件 | app启动或者依赖资源,保证其可访问和完整性 |
以下部分不建议添加单元测试
Model 层、具体网络请求
为什么要做单元测试
- 保证最小单元的正确性
- 检查函数是否符合预期(要达到什么目的)
- 验证边界条件(最大/最小、最坏/最好、最?/最?情况下是否满足)
- 异常情况考虑
- 保证重构顺利进行(重构了是否满足)
UI测试,更注重保证回归线和业务正确性。
如何做单元测试
单元测试原则
单元测试开发主要是基于以下三个要素:
**Given(条件):**配置测试的初始状态,比如造一些数据、mock一些对象等,这里我们可能需要OCMock这个库的辅助。
**When(测试条件):**对要测试的目标执行代码,调用你要测试的方法
**Then(结果检查):**对测试结果进行断言(成功 or 失败),对于结果做一个预判,并且通过编译来判断你的预判是否正确
- (void)testGetNameSpaceFromUUID{
NSDictionary *data = 0(0"aaa":@fo"type":@(@"namespace":@"bbb"})); // Given
NSString *nameSpace = [SCSCartInfoParser getNameSpaceFromUUID:@"aaa" withData:data]; // When
XCTAssert ([nameSpace isEqualToString:@"bbb"]); // Then
}
不要对私有方法进行测试(如果需要,请尝试拆离到单独的类)
UI 测试
UI测试更加关注交互和业务,用代码去模拟用户的行为。
比如通过按钮的sendActionsForControlEvents:
方法模拟点击,通过代理方法实现查看点击 cell 时间是否符合预期等。
单元测试技术
由于 XCTest 适合简单情况,存在阅读性差的问题,所以同时引入 Kiwi 框架,在复杂类和测试中可使用 Kiwi 来编写单元测试。
XCTest
XCTest
是自带的简单单元测试技术。
示例代码:
- (void)testRemoveTailOfZero {
NSString *givenStr1 = @"0.0023002300";
NSString *givenStr2 = @"000043003400";
NSString *givenStr3 = @"9.00";
NSString *givenStr4 = @"100.0";
XCTAssertTrue([[givenStr1 removeTailOfZero] isEqualToString:@"0.00230023"], @"移除末尾为0和.错误");
XCTAssertTrue([[givenStr2 removeTailOfZero] isEqualToString:@"000043003400"], @"移除末尾为0和.错误");
XCTAssertTrue([[givenStr3 removeTailOfZero] isEqualToString:@"9"], @"移除末尾为0和.错误");
XCTAssertTrue([[givenStr4 removeTailOfZero] isEqualToString:@"100"], @"移除末尾为0和.错误");
}
Kiwi
Kiwi是一个iOS平台十分好用的行为驱动开发(Behavior Driven Development,以下简称BDD)的测试框架,有着非常漂亮的语法,可以写出结构性强,非常容易读懂的测试。
优点
- 结构性强、可读性高
- 支持 mock、stub
- 支持参数捕获、异步测试
示例代码:
describe(@"NSString+Category", ^{
context(@"去除小数点末尾0", ^{
it(@"当0.0023002300时,应该去除后面两个0,即:0.00230023", ^{
[[@"0.0023002300".removeTailOfZero should] equal:@"0.00230023"];
});
it(@"当000043003400时,整数保持不便", ^{
[[@"000043003400".removeTailOfZero should] equal:@"000043003400"];
});
it(@"当9.00时,应该去除小数点后面两个0,即:9", ^{
[[@"9.00".removeTailOfZero should] equal:@"9"];
});
it(@"当100.0时,应该去除小数点后面一个0,即:100", ^{
[[@"100.0".removeTailOfZero should] equal:@"100"];
});
});
}
如过你打算使用 Kiwi,强烈建议你阅读以下教程:
TDD 的 iOS 开发初步以及 Kiwi 使用入门 – OneV
Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试 – OneV
stub
可以理解为测试桩,它能实现当特定的方法被调用时,返回一个指定的模拟值。如果你的测试用例需要一个伴生对象来提供一些数据,可以使用 stub 来取代数据源,在测试设置时可以指定返回每次一致的模拟数据。
spy
可以理解为侦查,它负责汇报情况,持续追踪什么方法被调用了,以及调用过程中传递了哪些参数。你能用它来实现测试断言,比如一个特定的方法是否被调用或者是否使用正确的参数调用,方法返回值。当你需要测试两个对象间的某些协议或者关系时会非常有用。
mock
与 spy 类似,但在使用上有些许不同。spy 追踪所有的方法调用,并在事后让你写断言,而 mock 通常需要你事先设定期望。你告诉它你期望发生什么,然后执行测试代码并验证最后的结果与事先定义的期望是否一致。
如何让代码更具备单元测试能力
按照 TTD 开发流程,应该先写测试用例,再编写组合业务代码。但是也存在待测试先行,在编写待测试代码时,我们应该遵循以下原则:
- 待测试的函数功能单一且明确
- 应该具有输入输出(以便测试环境 mock 和输出结果检查)
- 为了提高可测试性,应该将功能逻辑提取到单独的处理类中,而不是统一在 ViewController 中处理。
- 使用 BDD 思维来定义接口(思考一个对象的行为 (它的接口应该如何) 并且减少对实现的关注)
避免 ViewController 臃肿问题
传统 MVC 模式容易导致大量逻辑代码存在于 ViewContoller 中,大量私有方法导致无法进行单元测试。
对此,我们应该抽离中间件,用来处理数据和逻辑处理,通过中间件来进行单元测试,同时,使 ViewController 更加简洁和单一职责。
开启单元测试覆盖率
通过勾选 Xcode 的 CodeCoverage可以查看单元测试覆盖率和覆盖部分。
查看 Build Settings
如果需要配合自动化,可以借助 XcodeCoverage 库导出覆盖率查看
单元测试持续集成
待完成
Appium 自动化测试
优点:可以多语言, 有图形化操作
缺点:配置复杂。
踩坑
- WebDriverAgent 项目直接编译运行 Appium 包内容目录(/Applications/Appium Server GUI.app/Contents/Resources/app/node_modules/appium/node_modules/appium-webdriveragent)下的
- 修改 bundId 后还报错,点击 runer 中 build settings 中搜索 facabook 修改对应 bundId
参考
iOS 单元测试和 UI测试教程–raywenderlich
英文教程,从理论到实践操作。
Github+Fastlane+Jenkins 的持续集成测试–raywenderlich
WWDC17官方视频讲解,介绍我们应该够如果编写可测试的代码。
通过集成化工具 Bamboo 和 XcodeCoverage 查看测试覆盖率。如何写一个单元测试,OCMock 基本介绍和使用
TDD 的 iOS 开发初步以及 Kiwi 使用入门 – OneV
Kiwi 使用进阶 Mock, Stub, 参数捕获和异步测试 – OneV
王巍编写的对 Kiwi 框架的理解和使用,如何利用 Kiwi 中的 Mock和 stub。示例包含了对 Kiwi 和原始 XCTest 使用。
相关 Demo
-
Demo VVStack: 同时使用 Kiwi 和 XCTest 进行单元测试对比
-
PhotoData_Kiwi:将 objc.io 中测试相关的 Demo 使用 Kiwi 替换,详细描述对类初始化及各个公共方法的测试,以及使用 stub 和 mock 测试 Controller。
对objc.io文章的翻译,其中的测试板块内容,包含基本测试理论,单元测试实际集成经验等内容,非常建议阅读。
主要介绍对元素的操作。