Effective Kotlin中文版
  • ReadMe
  • 前言
  • 第一部分:良好的代码
    • 第一章:安全性
      • 第1条:限制可变性
      • 第2条:最小化变量的作用域
      • 第3条:尽可能消除平台类型
      • 第4条: 不要暴露需要推断的类型
      • 第5条:指明你期望的参数和状态
      • 第 6 条: 优先使用标准错误,而不是自定错误
      • 第7条:当返回结果可能缺失时,优先使 null 或 Failure
      • 第8条:妥善处理空值
      • 第9条: 使用 use 来关闭资源
      • 第10条:编写单元测试
    • 第二章:可读性
      • 第11条:为了可读性设计代码
      • 第12条:操作符的行为应该与其名称一致
      • 第13条:避免返回或操作 Unit?
      • 第14条: 在变量不清晰时指定其类型
      • 第15条:考虑显式引用接收者
      • 第16:属性应该代表状态,而非行为
      • 第17条:考虑使用具名参数
      • 第18条:遵守编程惯例
  • 第二部分:良好的设计
    • 第三章:可重用性
      • 第19条:不要重复知识
      • 第20条:不要重复实现常用算法
      • 第21条 使用属性代理来提取公共的属性模式
      • 第22条:当实现公共算法时使用泛型
      • 第23条:避免隐藏类型参数
      • 第24条:在使用泛型时考虑型变
      • 第25条:在不同的平台上提取公共模块进行重用
    • 第四章:抽象设计
      • 第26条:每个方法都应该基于单一的抽象级别而编写
      • 第27条:使用抽象来保护代码不受更改
      • 第28条:指定 Api 的稳定性
      • 第29条:考虑包装扩展 API
      • 第30条:最小化元素的可见性
      • 第31条:用文档定义合约
      • 第32条:遵守抽象合约
    • 第五章:对象的创建
      • 第33条:考虑使用工厂方法代替构造函数
      • 第34条:考虑带命名默认参数的主构造函数
      • 第35条:考虑为复杂的对象创建定义 DSL
    • 第六章:类的设计
      • 第36条:组合优于继承
      • 第37条:使用数据修饰符来表示一组数据
      • 第38条:使用函数类型而不是接口来传递操作和行为
      • 第39条:类层次结构优于标签类
      • 第40条:遵守 equals 的合约
      • 第41条:遵守 hashCode 的合约
      • 第42条:遵守 compareTo 的合约
      • 第43条: 考虑将 API 的非必要部分提取到扩展函数中
      • 第44条:避免在成员中定义扩展
  • 第三部分:性能
    • 第七章:让开发成本更低
      • 第45条:避免不必要的对象创建
      • 第46条:给高阶函数使用 inline 修饰符
      • 第47条:考虑使用内联类
      • 第48条:消除过时的对象引用
    • 第八章:高效的集合处理
      • 第49条:在具有多个处理步骤的大型集合上,优先使用 Sequence
      • 第50条:限制操作步骤的数量
      • 第51条:性能关键处考虑使用原语的数组
      • 第52条:考虑使用可变集合
Powered by GitBook
On this page
  1. 第一部分:良好的代码
  2. 第一章:安全性

第10条:编写单元测试

Not Kotlin-specific、Basics

在本章中,你已经看到了很多使代码安全的方法,但最终的方法是使用不同种类的测试。一种是从用户的角度检查应用程序的行为是否正确,这种方式通常是管理层所认可的,因为这通常是他们的主要目标:应用程序表面上(而不是内部)可以正确的运行。这类测试甚至根本不需要开发人员来做,它们可以由足够数量的测试人员来处理,或者,从长远来看更好的方法是:由测试工程师编写自动测试程序以达到这个目标。

这样的测试对程序员来说有用,但远远不够,它们不足以建立适当的保障,以确保系统的具体元素能够正确地运行。它不能提供在开发过程中有用的快速反馈。为此,我们需要一种对开发人员更有用的测试,它就是由开发人员编写的:单元测试。下面是一个单元测试示例,检查函数 fib 计算的斐波那契数前五个是否正确:

@Test
fun `fib works correctly for the first 5 position`() {
    assertEquals(1, fib(0))
    assertEquals(1, fib(1))
    assertEquals(2, fib(2))
    assertEquals(3, fib(3))
    assertEquals(5, fib(4))
}

在写单元测试时,我们通常会检查:

  • 常规用例(比较松的触发途径)—— 我们期望函数被使用的典型方式,就像上面的例子那样,用于测试函数对于几个少数的、较小的输入是否有效

  • 常规错误或潜在的问题 —— 我们认为的一些可能无法让函数正常工作,或过去被证明有问题的情况

  • 边界检查和非法入参 —— 我们可能会检查非常大的数字,如 Int.MAX_VALUE,对于一个可空的对象,它可能是空的或者填充了空值的对象。 对于斐波那契数列来说没有负数的输入,所以我们可以检查这个函数输入负数时的行为

单元测试在开发过程中非常有用,因为它们可以快速反馈所实现的函数如何工作。测试用例会不断积累,所以你可以轻松地使用以前的用例。它们还可以检查难以手动测试的用例,甚至有种方法叫测试开发驱动(Test Driven Development即TDD),在这种方法中,我们首先编写一个单元测试用例,然后实现一个函数去验证它。

单元测试最大的优点是:

  • 经过单元测试的代码往往更可靠,还有心理上的安全感,当元素经过良好的测试时,我们会更自信的去使用它们

  • 当一个元素被正确测试过,我们就不怕重构它,因此,经过良好测试的程序往往会变得越来越好。另一方面,在未经测试的程序中,开发人员害怕接触遗留代码,因为他们很可能会在不知情地引入意外的错误

  • 使用单元测来检查某些东西是否正常工作,通常比手动检查要快得多,更快的反馈使程序发展更快更丝滑,它还有助于降低修复 bug 的成本:如果你越快找到 bug,你修复它们的成本就越低

显然地,单元测试也有缺点:

  • 编写单元测试需时间,尽管从长远来看,好的单元测试更能节省我们的时间,因为花在调试和寻找 bug 上的时间更少,我们还节省了大量的的时间,因为运行单元测试远比手动测试或其他类型的自动化测试要快得多

  • 我们需要主动调整我们的代码,使其可测试。这样的更改通常是困难的,但是它们通常也会迫使开发人员使用良好和完善的体系结构

  • 编写好的单元测很难,它需要的技能和理解是正交于其他开发手段的。编写糟糕的单元测试弊大于利。每个人都需要学习如何正确地对代码进行测试。先了解软件测试或测试驱动开发(TDD)这样的教程是很有用的。

我们最大的挑战是编写有效的单元测试,和编写出支持单元测试代码的技能。有经验的 Koin 开发人员应该要学会这项技能,或至少学会对代码的重要部分进行单元测试, 这些部分是

  • 复杂的功能

  • 随着时间推移,可能会发生变化、或被重构的部分

  • 业务逻辑

  • 公共 API

  • 有被破坏倾向的部分

  • 被我们修复的产品 bug

我们不需要因为它难就停下脚步,写测试用例是对应用程序的可靠性和长期可维护性的投资。

总结

本章从一个反思开始,即我们的程序应该以正确的方式运行,它可以通过使用本章提供的良好实践来支持。但在此之上,确保我们的应用程序行为正确的最好方法是通过测试来检查它,特别是单元测试。这就是为什么一个负责任的讲解程序安全的章节,至少需要一个简单的章节来介绍单元测试。就像负责任的业务应用程序至少需要一些单元测试一样。

Previous第9条: 使用 use 来关闭资源Next第二章:可读性

Last updated 2 years ago