iOS 单元测试

一、什么是单元测试

单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法。

通常来说,程序员每修改一次程序就会进行最少一次单元测试,在编写程序的过程中前后很可能要进行多次单元测试,以证实程序达到软件规格书要求的工作目标,没有程序错误;虽然单元测试不是什么必须的,但也不坏,这牵涉到项目管理的政策决定。

每个理想的测试案例独立于其它案例;为测试时隔离模块,经常使用stubs、mock或fake等测试马甲程序。单元测试通常由软件开发人员编写,用于确保他们所写的代码匹配软件需求和遵循开发目标。它的实施方式可以是非常手动的(通过纸笔),或者是做成构建自动化的一部分。

二、为什么需要单元测试

优点

  • 避免低级错误,保证代码的质量
    疏忽造成的拼写错误,注意力不足造成调用的错误,对 API 望文生义的理解造成的方法调用错误等。根据墨菲定律,如果事情有变坏的可能,不管这种可能性有多小,它总会发生。而单元测试可以作为一个很好的补充。

  • 代码可以通过编译器检查语法的正确性,却不能保证代码逻辑是正确的,尤其包含了许多单元分支的情况下,单元测试可以保证代码的行为和结果与我们的预期和需求一致。

  • 及早发现问题、压力、风险提前释放到开发阶段。


    20180204141457523.png
  • 减少调试时间
    在缺少宿主程序的情况下,单元测试可以充当宿主程序。而即使存在宿主程序,单元测试相对于宿主程序也有着入口简单,方便执行的优点,不再需要通过复杂的流程才能够进行对应方法的测试,大大减少调试时间。

  • 增加可维护性、可扩展性
    这是我最推崇单元测试的原因,随着代码量膨胀,代码和代码之间的边界越来越模糊,新代码的加入对旧代码的影响并不是都能够通过逻辑推导获知。但对一个模块添加足够的单元测试后,新代码的加入可以在第一时间内测试完毕对旧逻辑的影响,增加整个模块的可维护性,减少这部分的测试工作,将更多的时间投入到更有意义的事情上去,如喜闻乐见的重构。

  • 为了保证可行的可持续的单元测试,程序单元应该是低耦合的,否则,单元测试将难以进行。

  • 帮助改善设计
    接上一条,重构是贯穿于项目工程的一件任务,随着时间推移,项目总归会慢慢产生各种技术债务,渐进式的重构是很好的还债手段。单元测试可以第一时间揭示重构带来的问题,让我们大胆地进行调整,改善既有设计,实现一个良好的循环。

缺点

  • 不能减少研发的代码量,反而会花费很多精力在编写单元测试上,增加了开发成本,而且对开发人员的要求也会更高。

  • 对于小项目来说,是否执行单元测试意义不大。

  • 单元测试聚焦的是一个模块单元的功能完整性和健壮性,但是模块间互动可能带来的问题并不属于单元测试的范畴,同时也有很大部分的界面测试和功能测试仍旧离不开测试工程。

三、哪些需要单元测试

单元测试侧重的是逻辑测试和接口测试。实际操作过程中,要自下而上进行。从最基础的 Base 层,往上写测试。确保基础的 Model,Manager 测试通过,才开始为 Controller 编写测试,因为这部分业务是最复杂的,也是最容易改变的。

1、网络数据层;
2、公共类中的公开方法;
3、业务逻辑层;
4、修复Bug的测试。

四、单元测试准则

  • 保持单元测试小巧, 快速
    理论上, 任何代码提交前都应该完整跑一遍所有测试套件. 保持测试代码执行迅捷能够缩短迭代开发周期。

  • 单元测试应该是全自动且无交互
    测试套件通常是定期执行的, 执行过程必须完全自动化才有意义. 需要人工检查输出结果的测试不是一个好的单元测试。

  • 让单元测试很容易跑起来
    对开发环境进行配置, 最好是敲条命令或是点个按钮就能把单个测试用例或测试套件跑起来。

  • 对测试进行评估
    对执行的测试进行覆盖率分析, 得到精确的代码执行覆盖率, 并调查哪些代码未被执行。

  • 立即修正失败的测试
    每个开发人员在提交前都应该保证新的测试用例执行成功, 当有代码提交时, 现有测试用例也都能跑通。

如果一个定期执行的测试用例执行失败, 整个团队应该放下手上的工作优先解决这个问题。

  • 把测试维持在单元级别
    单元测试即类 (Class) 的测试。一个 “测试类” 应该只对应于一个 “被测类”,并且 “被测类” 的行为应该被隔离测试。必须谨慎避免使用单元测试框架来测试整个程序的工作流,这样的测试既低效又难维护。工作流测试 (译注: 指跨模块/类的数据流测试) 有它自己的地盘, 但它绝不是单元测试,必须单独建立和执行。

  • 由简入繁
    最简单的测试也远远胜过完全没有测试. 一个简单的 “测试类” 会促使建立 “被测类” 基本的测试骨架, 可以对构建环境, 单元测试环境, 执行环境以及覆盖率分析工具等有效性进行检查, 同时也可以证明 “被测类” 能够被整合和调用.

  • 保持测试的独立性
    为了保证测试稳定可靠且便于维护, 测试用例之间决不能有相互依赖, 也不能依赖执行的先后次序.

  • 合理的命名测试用例
    确保每个方法只测试 “被测类” 的一个明确特性, 并相应的命名测试方法. 典型的命名俗定是 test[what], 比如 testSaveAs(), testAddListener(), testDeleteProperty() 等.

  • 只测公有接口
    单元测试可以被定义为 通过类的公有 API 对类进行测试. 一些测试工具允许测试一个类的私有成员, 但这种做法应该避免, 它让测试变得繁琐而且更难维护. 如果有私有成员确实需要进行直接测试, 可以考虑把它重构到工具类的公有方法中. 但要注意这么做是为了改善设计, 而不是帮助测试.

  • 看成是黑盒
    站在第三方使用者的角度, 测试一个类是否满足规定的需求. 并设法让它出问题.

  • 看成是白盒
    毕竟被测试类是程序员自写自测的, 应该在最复杂的逻辑部分多花些精力测试.

  • 芝麻函数也要测试
    通常建议所有重要的函数都应该被测试到, 一些芝麻方法比如简单的 setter 和 getter 都可以忽略. 但是仍然有充分的理由支持测试芝麻函数:
    <1>芝麻 很难定义. 对于不同的人有不同的理解.
    <2>从黑盒测试的观点看, 是无法知道哪些代码是芝麻级别的.
    <3>即便是再芝麻的函数, 也可能包含错误, 通常是 “复制粘贴” 代码的后果:
    因此建议测试所有方法. 毕竟芝麻用例也容易测试.

  • 先关注执行覆盖率
    区别对待 执行覆盖率 和 实际测试覆盖率. 测试的最初目标应该是确保较高的执行覆盖率. 这样能保证代码在 少量 参数值输入时能执行成功. 一旦执行覆盖率就绪, 就应该开始改进测试覆盖率了. 注意, 实际的测试覆盖率很难衡量 (而且往往趋近于 0%).

  • 覆盖边界值
    确保参数边界值均被覆盖. 对于数字, 测试负数, 0, 正数, 最小值, 最大值, NaN (非数字), 无穷大等. 对于字符串, 测试空字符串, 单字符, 非 ASCII 字符串, 多字节字符串等. 对于集合类型, 测试空, 1, 第一个, 最后一个等. 对于日期, 测试 1月1号, 2月29号, 12月31号等. 被测试的类本身也会暗示一些特定情况下的边界值. 要点是尽可能彻底的测试这些边界值, 因为它们都是主要 “疑犯”.

  • 提供一个随机值生成器
    当边界值都覆盖了, 另一个能进一步改善测试覆盖率的简单方法就是生成随机参数, 这样每次执行测试都会有不同的输入.
    想要做到这点, 需要提供一个用来生成基本类型 (如: 浮点数, 整型, 字符串, 日期等) 随机值的工具类. 生成器应该覆盖各种类型的所有取值范围.

  • 对于一般性代码而言, 保证测试代码尽可能少是一个重要目标.

  • 使用显式断言
    应该总是优先使用 assertEquals(a, b) 而不是 assertTrue(a == b), 因为前者会给出更有意义的测试失败信息. 在事先不确定输入值的情况下, 这条规则尤为重要, 比如之前使用随机参数值组合的例子.

  • 提供反向测试
    反向测试是指刻意编写问题代码, 来验证能否正确的处理错误.

  • 代码设计时谨记测试
    编写和维护单元测试的代价是很高的, 减少代码中的公有接口和循环复杂度是降低成本, 使高覆盖率测试代码更易于编写和维护的有效方法.

五、测试工具及使用

  • XCTest
  • XCTestUI
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言前面四篇文章认识了单元测试,了解了单元测试的简单使用方法,后面的文章将会介绍单元测试的更加全面的使用准则以及具...
    修_远阅读 751评论 3 2
  • 单元测试不是一个小工程,需要多用些时间才能做好,不要希望通过这个文章就能掌握单元测试,这只是一个入门,需要自己动手...
    勇不言弃92阅读 7,876评论 9 60
  • 目录 一、怎么添加测试类 二、怎么运行测试类 三、怎么查看覆盖率 四、测试类怎么编写(一、Test) 五、测试类怎...
    dvlproad阅读 5,690评论 0 25
  • 此篇文章并非原创, 转载链接:https://www.jianshu.com/p/c54f0cc08c20 单元测...
    MissLu16阅读 962评论 0 4
  • 找出要测试的内容 在编写任何测试之前,了解基础知识很重要。您需要测试什么? 如果您的目标是扩展现有应用程序,则应首...
    Joker_King阅读 893评论 0 1