单元测试是众所周知的做法,但还有很大的改进空间!在这篇文章中,我们讨论最有效的单元测试最佳实践,包括在此过程中最大化自动化工具的方法。我们还将讨论代码覆盖率、模拟依赖关系和整体测试策略。
什么是单元测试?
单元测试是测试应用程序的各个单元或组件的做法,以验证每个单元是否正常工作。一般来说,一个单元应该是应用程序的一小部分------在 Java 中,它通常是一个类。请注意,我在这里并没有严格定义"单元",而是由开发人员决定每个测试的测试代码的范围。
人们有时会将术语"单元测试"与"集成测试"或"端到端测试"进行对比。区别在于,通常,单元测试是为了验证单个可测试单元的行为,而集成测试是验证多个组件一起或整个应用程序的行为。正如我所说,"单元"的构成并没有严格定义,每个测试的范围由您决定。
为什么要进行单元测试?
单元测试是一种经过验证的确保软件质量的技术,具有很多好处。以下是进行单元测试的几个重要原因:
单元测试验证您的每个软件不仅现在可以正常工作,而且在未来也可以继续工作,为未来的开发提供坚实的基础。
单元测试可以在生产过程的早期阶段识别缺陷,从而降低在开发周期后期修复这些缺陷的成本。
经过单元测试的代码通常重构起来更安全,因为可以快速重新运行测试以验证行为没有改变。
编写单元测试迫使开发人员考虑生产代码的设计如何,以使其适合单元测试,并使开发人员从不同的角度看待他们的代码,鼓励他们在实现中考虑极端情况和错误情况。
在代码审查过程中包括单元测试可以揭示修改后的或新的代码应该如何工作。另外,审阅者可以确认测试是否良好。
不幸的是,很多时候,开发人员要么根本不编写单元测试,要么不编写足够的测试,要么不维护它们。我理解------单元测试有时编写起来很棘手,或者维护起来很耗时。有时需要满足最后期限,感觉编写测试会让我们错过那个最后期限。但是,没有编写足够的单元测试或没有编写良好的单元测试是一个很容易陷入的危险陷阱。
因此,请考虑我以下关于如何编写干净、可维护、自动化测试的最佳实践建议,这些测试可以让您以最少的时间和精力享受单元测试的所有好处。
单元测试最佳实践
让我们看一下构建、运行和维护单元测试的一些最佳实践,以获得最佳结果。
单元测试应该值得信赖
如果代码被破坏并且仅当代码被破坏时测试必定失败。如果不是,我们就不能相信测试结果告诉我们的内容。
单元测试应该是可维护和可读的
当生产代码发生变化时,测试通常需要更新,也可能需要调试。因此,测试必须易于阅读和理解,不仅对于编写测试的人来说,而且对于其他开发人员来说也是如此。始终为了清晰和可读性而组织和命名您的测试。
单元测试应该验证单个用例
好的测试只能验证一件事,而且只能验证一件事,这意味着它们通常会验证单个用例。遵循此最佳实践的测试更简单且更易于理解,这有利于可维护性和调试。验证不止一件事的测试很容易变得复杂且维护起来耗时。不要让这种事发生。
另一个最佳实践是使用最少数量的断言。有些人建议每个测试只使用一个断言(这可能有点过于严格);这个想法是专注于仅验证您正在测试的用例所需的内容。
单元测试应该隔离
测试应该可以在任何机器上以任何顺序运行,而不会相互影响。如果可能,测试不应依赖于环境因素或全局/外部状态。具有这些依赖项的测试更难运行并且通常不稳定,这使得它们更难调试和修复,并且最终花费的时间比节省的时间更多(请参阅上面的trustworthy)。
几年前,Martin Fowler撰写了有关 "孤独"与"社交"代码的文章,以描述应用程序代码中的依赖项使用情况,以及如何相应地设计测试。在他的文章中,"独立"代码不依赖于其他单元(它更加独立),而"社交"代码确实与其他组件交互。如果应用程序代码是孤独的,那么测试很简单,但对于被测试的社交代码,您可以构建"孤独"或"社交"测试。"社交测试"将依赖于真实的依赖关系来验证行为,而"单独测试"则将被测代码与依赖关系隔离开来。您可以使用模拟来隔离被测代码,并为"社交"代码构建"单独"测试。我们将在下面看看如何做到这一点。
一般来说,使用模拟来处理依赖关系使我们作为测试人员的生活更轻松,因为我们可以为社交代码生成"单独的测试"。对复杂代码的社交测试可能需要大量设置,并且可能违反隔离和可重复的原则。但由于模拟是在测试中创建和配置的,因此它是独立的,我们可以更好地控制依赖项的行为。另外,我们可以测试更多代码路径。例如,我可以返回自定义值或从模拟中抛出异常,以覆盖边界或错误条件。
单元测试应该自动化
确保测试在自动化过程中运行。这可以是每天,也可以是每小时,也可以是在持续集成或交付过程中。团队中的每个人都需要能够访问和审查这些报告。作为一个团队,讨论您关心哪些指标:代码覆盖率、修改后的代码覆盖率、正在运行的测试数量、性能等。
通过查看这些数字可以学到很多东西,这些数字的大幅变化通常表明可以立即解决回归问题。
单元测试应验证所有细节、极端情况和边界条件等。应更加谨慎地使用组件、集成、UI 和功能测试,以验证 API 或应用程序作为一个整体的行为。手动测试应该占整个金字塔结构的最小百分比,但对于发布验收和探索性测试仍然有用。该模型为组织提供了高水平的自动化和测试覆盖率,以便他们可以扩大测试工作并将与构建、运行和维护测试相关的成本保持在最低水平。
单元测试应在有组织的测试实践中执行
为了推动各级测试的成功,并使单元测试过程可扩展且可持续,您将需要一些额外的实践。首先,这意味着在编写应用程序代码时编写单元测试。一些组织在应用程序代码之前编写测试(测试驱动或行为驱动编程)。重要的是测试与应用程序代码齐头并进。测试和应用程序代码甚至应该在代码审查过程中一起审查。评论可以帮助您理解正在编写的代码(因为它们可以看到预期的行为)并改进测试!