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

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

一、为什么需要单元测试

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

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

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

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

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

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

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

三、配置运行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方法,所以可以把需要测试完成后销毁的内容写在这个里,以便保证下面的测试不受本次测试影响

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

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

(7) measureBlock用来测试性能

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

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

5.2.1. 基本断言的逻辑测试

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

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

函数声明
函数实现
测试用例

5.2.2. 异步测试

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

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

函数声明
测试用例

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

delegate protocol定义
在函数内部要调用delegate的方法通知测试用例已经完成

[WWDC 2017NEW]XCTWaiter

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

引入XCTWaiter的异步测试用例

5.2.3. 加上OCMock完成的Mock测试

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

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

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

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

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

类声明
类实现

(5.2.3.1)Method Mock:

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

(5.2.3.2)Partial Mock or Protocol Mock

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

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

(5.2.3.3)Delegate to Other  Method or Block

上代码表示这个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

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/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容