APP重构之路(三) 引入单元测试

APP重构之路(一) 网络请求框架

APP重构之路(二) Model的设计

APP重构之路(三) 引入单元测试


重构的时候我们需要一个模具,让我们能够大胆修改的同时确保结果的正确性,这个时候就要引入“单元测试”了。

前言

本文没有给出任何测试代码,或者是在教你如何编写一份具有良好测试性的代码,而是阐述在重构过程中单元测试的重要性与实现方法,关于代码可测试性相关的内容我会另开一篇文章去具体阐述。(画个饼)

一、为什么要引入单元测试

在开发过程中我们会遇到这样一些问题:

  • 面对需要重构庞大的模块代码时无从下手
  • 修改了一处地方却在另一处地方引发了新的bug
  • 扩展新功能的同时导致旧代码出现bug
  • 在测试人员难以覆盖到的基础功能接口出现了bug
  • 出现了一种难以重现的特殊边界条件触发的bug

另外我们也许还会遇到一些这样的模块:

  • A模块依赖于B模块的结果,但是B模块尚未开发完成
  • 模块状态过于复杂,手工测试需要耗费大量时间
  • 模块业务与时间节点相关,手工测试难以覆盖

这个时候也许能够利用经验和丰富的debug技巧来解决这些问题,但是很多时候我们的处理并不完美,因为我们缺少了一个规范,在编码过程中难以顾及其他模块的影响,这个时候,我们就需要引入单元测试

二、单元测试的价值

可维护性增强

当在对代码进行修改时,利用单元测试就能够清晰的知道是否破坏了老的业务逻辑,这样大大减少了回归出错的可能性。而当我们从测试那里获得了一个bug时,就可以通过测试用例去还原,当我们这个测试通过后,这个bug也就解决了,而且这个bug fix的测试用例也保证了这个bug以后不会再次出现。

降低重构难度

有了单元测试的保障,我们可以比较大胆的进行重构设计,而单元测试也会成为重构时很好的一个模具。当然在重构时也需要对单元测试进行重构,但是和可靠性相比,这种额外的负担是值得去承受的

减少调试时间

在调试中,我们很多时候都需要花费一些额外的时间来触发需要调试的代码,但是在单元测试中,我们能够针对需要调试的代码构建相关的测试用例,方便的进行反复的测试与模拟,大大减少了调试的时间。

减少低级错误

测试的存在价值就是为我们发现并解决错误,单元测试更是如此,当我们对自己的代码进行单元测试时,就能容易的排除掉一些非常低级的错误,起码我们能够保证在一些正常的情况下代码是可以正确工作的。

描述代码

好的代码就是一份好的文档,单元测试更是如此。一份好的单元测试能够描述在对应的情况下,代码应该有如何的预期表现,那么别人只需要查看测试用例就能清楚的知道代码的功能。

提高代码质量

一份代码如果和其他代码强耦合,它是难以被测试的,所以为了测试,开发人员会被驱使写出低耦合、可扩展的代码。

三、单元测试方案

单元测试中有测试驱动开发(TDD)与行为驱动开发(BDD)两种思路

测试驱动开发(TDD)

  • 根据需求与接口先编写测试用例
  • 根据测试用例编写业务代码
  • 开发效率低
  • 资源耗费大
  • 测试覆盖率高

行为驱动开发(BDD)

  • 通过测试用例描述代码行为
  • 通过自动运行测试用例快速反馈
  • 通过Mock作为相关代码模块的替身
  • 开发效率较高
  • 资源耗费较低
  • 测试覆盖率较TDD要低

基于目前项目的情况与开发流程,我选择了BDD作为测试框架,并会选择使用XCTest + OCMock + OCHamcrest的方案,以下是三个框架的介绍:

XCTest

XCTest 可以完成的事

  • 基本断言的逻辑判断
  • 异步测试
  • 性能测试

为什么选择 XCTest

  • XCode原生的测试框架,能够更好适应Apple之后的更新
  • XCTest有大量文档支持,上手难度较低
  • XCTest添加进项目后只是作为项目测试框架,并不会影响到打包等一些东西

OCMock

为什么需要 OCMock

mock即为模拟,OCMock可以伪造(模拟)一个对象,给它一些预设的值之类的,并进行对应的验证

比如在我需要测试WiFi直连模块时,我需要一个WiFi才能测试直连功能,这个时候我们就可以利用OCMock,去模拟一个WiFi对象,它可以是模拟成风险WiFi,也可以模拟成免费WiFi,这样我们的直连模块的测试就完全独立于WiFi对象,可以方便的进行测试。

OCMock 可以完成的事

  • 创建一个模拟对象,模拟一个特定对象的行为,排除一些外部类的干扰
  • 构造自己的用例进行验证
  • 对已有方法进行重定义,以自己定义的逻辑进行交互
  • 判断函数是否执行过

为什么选择 OCMock

  • 原生XCTest并不支持Mock功能
  • OCMock是专门为iOS与OS X进行Mock测试的开源项目,拥有超过5000+ app使用,1100万+下载量
  • OCMock使用Apache 2.0协议,能够在需要时候修改代码满足需要并作为开源或商用产品发布/销售
  • OCMock有官方文档,资料齐全

OCHamcrest

OCHamcrest 可以完成的事

  • 更高级的断言
  • 断言可扩展性
  • 支持结构体的断言

为什么选择 OCHamcrest

  • 相对于另一个断言框架 Expecta ,OCHamcrest更为成熟,Expecta可能会导致断言结果错误
  • XCTest 内置断言并不充分,复杂条件下的判断需要编写大量断言代码
  • 利用OCHamcrest的扩展性能够将格式化的自定义log输出到日志文件,提供更多可以定制化而且详细的信息

四、整体测试框架

屏幕快照 2018-08-22 上午11.30.25.png

五、应该测试什么 应该怎么测试

测试的原则

  • 快速:这样才不会介意去运行
  • 独立:一个测试不应该耦合于另一个测试
  • 可重复:每次测试的结果应该一致
  • 可验证:结果应该是成功/失败,而不是一个解释性的日志文档

测试的内容

在写任何测试前,应该明确应该要测试什么,一般的情况下,单元测试应该包括这些内容:

  • 核心功能测试
  • 边界条件
  • 错误处理

测试的思路

针对目前项目情况,我会使用单元测试与人工测试相结合的方式去进行,因为目前我们大部分功能都是与UI牵连,不能完全依靠单元测试去完成所有的测试工作,但是我们可以将逻辑部分进行分离,举网络连接模块为例:

请求流程

我们可以对界面相关的部分测试进行拆解,在逻辑部分实现单元测试,而人工测试部分单纯检查整个直连流程和界面部分是否正常。

这样能够避免人工测试时依赖于逻辑的情况,比如在需要测试发起100个请求后模块是否会出现问题时,无需依靠手工去真的连接100次,只需要在单元测试中模拟进行100次连接,并查看结果是否正确就可以。

应该测试的对象

在项目中,我们有大量的类,全部覆盖单元测试是不现实的,我们需要进行挑选。以下是我列举的一些因素。

1.数据相关

比如在本地数据存储模块中,我们需要保存不同的数据,这时候我们可以通过单元测试构造不同的测试数据进行保存,查看是否保存成功,数据部分是单元测试最需要覆盖的部分。

2.逻辑相关

比如在连接模块中,需要对部分请求结果进行过滤,这就是一个逻辑,针对这种逻辑,可以在单元测试中进行测试是否过滤成功,而人工测试则无需关注过滤的逻辑,仅仅需要关注过滤后界面是否正常显示。

3.多状态的模块

比如在连接模块中,连接的状态就有8种,包括了连通性检查、连接中、已连接等,这些状态能够利用单元测试很好的模拟出来,这样就解决了人工测试下难以模拟不同状态转换的问题。

六、总结

我们写代码最终的目的只有两个:实现需求与提高代码质量,在保证完成需求的前提下,增加单元测试能提高代码的质量与可维护性,纵使在引入了单元测试后,我们也许会面临增加了研发的代码量,花费更多精力在编写单元测试上,增加了开发成本,但我认为相比于单元测试带来的优势,这些是能够克服的。

更多内容可以关注我的博客

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

推荐阅读更多精彩内容

  • 前言: 对于单元测试来说,我想大部分同行,在项目中,很少会用到,也有一大部分,知道单元测试这个东西,但是确切的说没...
    麦穗0615阅读 3,211评论 4 44
  • 单元测试不是一个小工程,需要多用些时间才能做好,不要希望通过这个文章就能掌握单元测试,这只是一个入门,需要自己动手...
    勇不言弃92阅读 7,781评论 9 60
  • 前言 如果有测试大佬发现内容不对,欢迎指正,我会及时修改。 大多数的iOS App(没有持续集成)迭代流程是这样的...
    默默_David阅读 1,644评论 0 4
  • 大多数的iOS App (没有持续集成)迭代流程是这样的: 也就是说,测试是发布之前的最后一道关卡。如果bug不能...
    伯牙呀阅读 4,864评论 1 22
  • “再忍忍这高中三年,到了大学你就自由了”这句话,应该是大家最熟悉的“谎言”之一。其实仔细想一下,这句话也没错,...
    yyyy24阅读 95评论 0 0