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