确定您需要测试的内容以及可以排除的内容。
上一篇文章介绍了测试用例的基础知识以及它们应包含的内容。本文从技术角度深入探讨测试用例的创建,详细说明了每个测试中应包含的内容以及应避免的内容。本质上,您将了解“测试什么”或“不测试什么”这个古老问题的答案。
通用指南和模式
值得注意的是,无论您进行单元测试、集成测试还是端到端测试,特定的模式和要点都至关重要。这些原则可以并且应该应用于这两种类型的测试,因此它们是一个很好的起点。
保持简单
在编写测试时,最重要的事情之一是记住保持简单。重要的是要考虑大脑的容量。主要生产代码占用了大量空间,几乎没有留下额外的复杂性空间。对于测试来说尤其如此。
如果可用的脑力空间较少,您可能会在测试工作方面变得更加放松。这就是为什么在测试中优先考虑简单性至关重要。事实上,Yoni Goldberg 的 JavaScript 测试最佳实践强调了黄金法则的重要性——您的测试应该感觉像一个助手,而不是一个复杂的数学公式。换句话说,您应该能够一目了然地理解您的测试意图。
您应该在所有类型的测试中都力求简单,无论其复杂程度如何。事实上,测试越复杂,简化它就越重要。实现此目的的一种方法是通过扁平化测试设计,其中测试尽可能保持简单,并且仅测试必要的内容。这意味着每个测试应仅包含一个测试用例,并且测试用例应侧重于测试单个、特定的功能或特性。
从这个角度考虑:在读取失败的测试时,应该很容易识别出哪里出了问题。这就是为什么保持测试简单易懂很重要。这样做可以让您在出现问题时快速识别和修复问题。
测试值得测试的内容
扁平化测试设计还鼓励关注,并有助于确保您的测试有意义。请记住,您不希望仅仅为了覆盖率而创建测试——它们应该始终有目的。
不要测试实现细节
测试中的一个常见问题是,测试通常旨在测试实现细节,例如在组件或端到端测试中使用选择器。实现细节是指您的代码用户通常不会使用、看到甚至了解的事物。这可能会导致测试中出现两个主要问题:假阴性和假阳性。
当测试失败时,即使被测代码是正确的,也会发生假阴性。当实现细节因应用程序代码的重构而更改时,可能会发生这种情况。另一方面,当测试通过时,即使被测代码不正确,也会发生假阳性。
解决此问题的一种方法是考虑您拥有的不同类型的用户。最终用户和开发人员的方法可能不同,并且他们与代码的交互方式也可能不同。在计划测试时,必须考虑用户将看到或与之交互的内容,并使测试依赖于这些内容,而不是实现细节。
例如,选择不易更改的选择器可以使测试更可靠:数据属性而不是 CSS 选择器。有关更多详细信息,请参阅 Kent C. Dodds 的文章,或继续关注——一篇关于此主题的文章即将发布。
模拟:不要失去控制
模拟是在单元测试中有时在集成测试中使用的一个广泛概念。它涉及创建虚假数据或组件来模拟对应用程序具有完全控制权的依赖项。这允许进行隔离测试。
在测试中使用模拟可以提高可预测性、关注点分离和性能。并且,如果您需要进行需要人工参与的测试(例如护照验证),您将不得不使用模拟来隐藏它。出于所有这些原因,模拟是值得考虑的宝贵工具。
与此同时,模拟可能会影响测试的准确性,因为它们是模拟,而不是真实的用户体验。因此,在使用模拟和存根时,您需要注意。
您应该在端到端测试中模拟吗?
一般来说,不应该。但是,模拟有时可以成为救命稻草——所以让我们不要完全排除它。
想象一下这种情况:您正在为一个涉及第三方支付提供商服务的功能编写测试。您处于他们提供的沙盒环境中,这意味着没有发生实际交易。不幸的是,沙盒出现故障,从而导致您的测试失败。修复需要由支付提供商完成。您所能做的就是等待提供商解决问题。
在这种情况下,减少对您无法控制的服务依赖可能更有益。在集成测试或端到端测试中谨慎使用模拟仍然是明智的,因为它会降低您测试的置信度。
测试细节:应该做和不应该做
总而言之,一个测试包含什么?测试类型之间是否存在差异?让我们仔细看看针对主要测试类型量身定制的一些具体方面。
一个好的单元测试应该包含什么?
理想且有效的单元测试应该
- 专注于特定方面。
- 独立运行。
- 包含小规模场景。
- 使用描述性名称。
- 如果适用,遵循 AAA 模式。
- 保证全面的测试覆盖率。
应该 ✅ | 不应该 ❌ |
---|---|
尽可能保持测试的小规模。每个测试用例测试一件事。 | 对大型单元编写测试。 |
始终保持测试隔离,并模拟您需要的单元外部的事物。 | 包含其他组件或服务。 |
保持测试独立。 | 依赖以前的测试或共享测试数据。 |
覆盖不同的场景和路径。 | 最多将自己限制在愉快路径或负面测试中。 |
使用描述性测试标题,以便您可以立即了解您的测试是关于什么的。 | 仅按函数名称进行测试,结果不够描述性:testBuildFoo() 或 testGetId() 。 |
在这个阶段,力求良好的代码覆盖率或更广泛的测试用例范围,尤其是在这个阶段。 | 从每个类测试到数据库 (I/O) 级别。 |
一个好的集成测试应该包含什么?
理想的集成测试也与单元测试共享一些标准。但是,您还需要考虑几个额外的要点。一个好的集成测试应该
- 模拟组件之间的交互。
- 涵盖真实世界的场景,并使用模拟或存根。
- 考虑性能。
应该 ✅ | 不应该 ❌ |
---|---|
测试集成点:验证每个单元在彼此集成时是否可以优雅地协同工作。 | 隔离测试每个单元——那是单元测试的目的。 |
测试真实世界的场景:使用从真实世界数据派生的测试数据。 | 使用重复的自动生成的测试数据或其他不能反映真实世界用例的数据。 |
对外部依赖项使用模拟和存根,以保持对完整测试的控制。 | 创建对第三方服务的依赖项,例如,对外部服务的网络请求。 |
在每个测试之前和之后使用清理例程。 | 忘记在测试内部使用清理措施,否则由于缺乏适当的测试隔离,可能会导致测试失败或假阳性。 |
一个好的端到端测试应该包含什么?
全面的端到端测试应该
- 复制用户交互。
- 包含重要场景。
- 跨越多个层。
- 管理异步操作。
- 验证结果。
- 考虑性能。
应该 ✅ | 不应该 ❌ |
---|---|
使用 API 驱动的快捷方式。了解更多。 | 对每个步骤使用 UI 交互,包括 beforeEach 钩子。 |
在每个测试之前使用清理例程。比在单元测试和集成测试中更注意测试隔离,因为这里出现副作用的风险更高。 | 忘记在每个测试后清理。如果您不清理剩余状态、数据或副作用,它们将影响稍后执行的其他测试。 |
将端到端测试视为系统测试。这意味着您需要测试整个应用程序堆栈。 | 隔离测试每个单元——那是单元测试的目的。 |
在测试内部使用最少或不使用模拟。仔细考虑您是否要模拟外部依赖项。 | 过度依赖模拟。 |
考虑性能和工作负载,例如,不要在同一测试中过度测试大型场景。 | 覆盖大型工作流程而不使用快捷方式。 |