第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

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

总结

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

Last updated