iOS单元测试初探以及OCMock使用入门

这段时间在工作之余研究了一下iOS的单元测试,试图在项目中引入开发自己写的白盒测试,积攒一些用例来减少之后修改代码后引发的缺陷。

一、为什么需要单元测试

写代码的过程中,我们发现有一些很基础功能的接口,测试在黑盒测试中很难发现缺陷。假如我们在开发过程中书写好一些用例,不仅能提高代码的质量,也能保证在之后的改动中及时发现改动会带来的错误。

二、选择什么框架来做单元测试

比较流行的有两种单元测试的思路模式,Test Driven Development(TDD)和Behavior Driven Development(BDD)。

TDD: 先根据需求或者接口情况编写测试,然后再根据测试来编写业务代码,这也就必然导致所有代码的public部分都会需要必要的测试。

BDD: 通过Given - When - Then三个流程化的条件来帮助开发确定应该测试什么

从一开始就舍弃掉了所有TDD的方案,因为就项目的现状来说实行起来效率会很低。对比了一下现有的BDD框架,还是想选用XCTest + OCMock的方案。原因主要是希望更好适应和Apple之后的更新,基本能满足需求,也更容易上手。

选择上也参考了一些现在主要采用同样方案的开源库:

iOS单元测试初探以及OCMock使用入门_第1张图片

三、配置运行XCTest需要的环境

Test target是和主Target在同一个project文件下,运行的另一个Target,设置编译和Target Radio同样的文件加上自己加上的测试文件

工程中使用了Cocoapods来管理第三方库的话,Test target也需要添加响应的第三方库依赖,这样所有在工程中能使用到的代码,也能在测试用例用使用。

四、如何搭建OCMock的运行环境

在搭建好XCTest的情况下,按照http://ocmock.org/ios/能很快配置好OCMock的运行环境。

五、如何使用XCTest + Mock来完成单元测试

(5.1)XCTest简介

(1) 整个工程中应该有多个XCTest文件,每一个XCTests都是只有.m文件的,继承自XCTestCase

(2) 每个XCTests.m文件中,必然包含一个setUp方法和一个tearDown方法

(3) 每个单元测试方法执行之前,XCTest会先执行setUp方法,所以可以把一些测试代码需要用的初始化代码写在这个方法里

(4) 每个单元测试方法执行完毕后,XCTest会执行tearDown方法,所以可以把需要测试完成后销毁的内容写在这个里,以便保证下面的测试不受本次测试影响

iOS单元测试初探以及OCMock使用入门_第2张图片

(5) 只执行test开头的用例,可以用前面加DISABLE_xxx 来表示废弃该用例

(6) command + U顺序运行测试用例

(7) measureBlock用来测试性能

(8) 运行结果如下图,成功和失败的错误用例显示在右侧,还可以选择导出代码的覆盖率 

iOS单元测试初探以及OCMock使用入门_第3张图片

(5.2)XCTest + OCMock能够帮助我们完成的事

5.2.1. 基本断言的逻辑测试

XCTest定义了一系列的断言来帮助代码的正确性,详细种类列举在https://developer.apple.com/documentation/xctest

* 例:有一个函数目的是生成在[base, end]之间的随机数

函数声明
iOS单元测试初探以及OCMock使用入门_第4张图片
函数实现
iOS单元测试初探以及OCMock使用入门_第5张图片
测试用例

5.2.2. 异步测试

代码中会有很多异步的场景需要验证,例如一些操作需要在不同的线程,在delegate method,或是callback中执行的操作。XCTest中主要使用XCTestExpectation来进行异步是否完成期望的测试。一般有两种方式完成异步的测试Block和Delegate:

* By Block: 如果超时或者是遇到断言的失败,该用例会失败。

函数声明
iOS单元测试初探以及OCMock使用入门_第6张图片
测试用例

 By Delegate: 使用delegate的方式会对原有代码产生影响。

delegate protocol定义
iOS单元测试初探以及OCMock使用入门_第7张图片
在函数内部要调用delegate的方法通知测试用例已经完成

[WWDC 2017NEW]XCTWaiter

WWDC2017中引入了XCTWaiter,简单来说就是通过delegate的方式把处理XCTExpectation的方法解耦,可以在delegate中处理超时,中断等异步测试用例的异常,关于Notification, Predicate, KVO的Expectation可以根据实际情况使用

iOS单元测试初探以及OCMock使用入门_第8张图片
引入XCTWaiter的异步测试用例

5.2.3. 加上OCMock完成的Mock测试

我们要测试的方法会引用很多外部依赖的对象,而我们没法控制这些外部依赖的对象。为了解决这个问题,我们需要用到Stub和Mock来模拟这些外部依赖的对象,从而控制它们。单独依靠XCTest难以完成Mock或者Stub,但是结合OCMock可以在测试代码中实现这以下功能。

*   Mock: 创建一个模拟对象,我们可以验证,修改它的行为

*   Stub: Mock对象的函数返回特定的值

*   Partial Mock: 重写Mock对象的方法

例:简单声明了一个类,再使用不同方式对它进行Mock:

iOS单元测试初探以及OCMock使用入门_第9张图片
类声明
iOS单元测试初探以及OCMock使用入门_第10张图片
类实现

(5.2.3.1)Method Mock:

以上代码表示这个exMock对象,的example1方法无论接受什么参数,都只会返回10

(5.2.3.2)Partial Mock or Protocol Mock

iOS单元测试初探以及OCMock使用入门_第11张图片

上面代码表示这个exMock对象,的example1方法无论接受什么参数,都只会返回10,并且实际的对象ex调用example1也只会返回10

*也可以创造遵循某个protocol的mock对象

(5.2.3.3)Delegate to Other  Method or Block

iOS单元测试初探以及OCMock使用入门_第12张图片

上代码表示这个exMock对象的example1方法已经用__switchExample1方法替换掉了,所以当调用[exMock example1:@(10)]的时候返回值为20

(5.2.3.1 ~5.2.3.3)是三种OCMock主要的Mock方法,利用它们我们可以在用例中排除一些外部类的干扰因素,构造自己的用例来进行验证,需要注意的是mock的对象在用例结束后要stopMocking,避免由于单例或者property导致的用例之间相互影响

(5.2.3.4) Expect-run-verify

iOS单元测试初探以及OCMock使用入门_第13张图片

OCMock有一个Expect-Run-Verify的模式,能够帮助我们验证是否期望的方法有被调用。上面的代码中,exMock期望example1会被调用,没有对参数做任何的限制。而在callExample1中假如像期望一样调用了example1,该用例会通过,否则失败。

这个模式在某些场景会适用到,比如验证app启动后是否进行一些必须做的一些操作和配置等。

六、Reference

(6.1)[iOS单元测试系列]单元测试框架选型:

http://zixun.github.io/blog/2015/04/11/iosdan-yuan-ce-shi-xi-lie-dan-yuan-ce-shi-kuang-jia-xuan-xing/

(6.2)XCTest Documentation: https://developer.apple.com/documentation/xctest

(6.3)XCTest实战: https://objccn.io/issue-15-2/

(6.4)Migrate to XCTWaiter for async tests?: https://github.com/Instagram/IGListKit/issues/610

(6.5)What’s New in Testing: https://developer.apple.com/videos/play/wwdc2017/409/

(6.6)Kiwi 使用进阶Mock, Stub, 参数捕获和异步测试: https://onevcat.com/2014/05/kiwi-mock-stub-test/

(6.7)OCMock Documentation: http://ocmock.org/

你可能感兴趣的:(iOS单元测试初探以及OCMock使用入门)