单元和集成测试
介绍
我目前的客户正在研究如何编写有效的单元测试和集成测试,以及如何重写代码以提高其可测试性。他们正在转向持续开发/持续集成的流程。我在这里的目的是展示我们的发展方向。本文将重点关注前端,但这些概念当然也可以应用于其他领域。
首先,他们有前端测试;不久前曾有人推动引入测试。据我所知,他们最初学习了测试驱动开发 (TDD),并专注于将代码覆盖率达到 100%。虽然他们掌握了这些信息是件好事,但他们在 TDD 的实施方面显然相当严格(甚至到了如果必须使用 TDD,他们宁愿不进行测试的地步),而对 100% 覆盖率的强烈追求似乎导致了较高覆盖率的测试(最终 10-20% 通常很难测试)的测试很脆弱。
此外,他们很少考虑所编写的代码类型,这不会影响他们所编写的测试类型。
未来……
我希望随着更多细节的揭示或我提出更好的解释,我将对接下来的许多部分进行更详细的描述。
测试多力多滋
我不打算在这里深入探讨测试金字塔(我们大多数人都见过)。只需注意,我们区分的是开发人员集成测试和 QA 集成测试(后者本文不再讨论)。
我们的目标
- 为了确保所发布的代码没有缺陷,
-
并证明它有效。
-
测试方法要切实可行;
-
对于需要测试的内容要谨慎。
-
为了减少缺陷(尽早发现错误)进入 QA,
-
并且部署更快、更简单。
定义:集成测试
- 不需要所有服务、环境和访问的实时版本(部分可以)。
- 范围不一定很广,但范围狭窄可能会更有效。
- 独立的代码单元相互连接时可以正常工作。
- 可以使用“忠实”的测试替身
定义:单元测试
- ATOMIC,最低级别(小而快)。
-
单一职责原则(SRP):“做好一件事。”
-
可重复、可靠且确定性。
-
展示具体的进展。
-
由于错误或需求改变而失败。
-
很容易理解为什么它会失败。
-
降低错误成本。
标准
- 易于书写
- 可读性
- 可靠的
- 快速地
总体测试策略
对于测试,没有“一成不变”的规则。
- 测试并非为了发现 bug。然而,bug 应该在修复的同时触发额外的测试。
- 测试开发人员对需求的理解。
- 将业务逻辑与服务器端或客户端集成逻辑分开。
- 在编写代码之前仔细考虑如何测试(可测试性设计)。
- 改进生产代码通常会简化测试代码。
- 在进行可测试性设计时,请勿测试原型、概念验证或实验代码(直到您知道它将成为生产代码)。
- 生产代码中的最佳实践并不等于测试中的最佳实践。
行业测试更好的实践
- 知道代码中的痛点在哪里。
- 并行工作和测试。
- 使其成为工作流程的一部分(提供责任/审查)。
- 命名约定(第一道防线/理解)。
- 拥抱神奇的数字。
- 重复是可以的(违反 DRY 原则)。
- 测试简单案例
- 边界案例
- 负面案例
- 捕获已知错误
- 优先事项
- 算法引擎
- 实用方法
- 核心业务逻辑方法
- 高风险服务
复杂
代码大小(“这个函数做的太多了。”)...
- 项目代码量不会改变,但方法中的语句数量可以改变。
- 少于 20 行:28% 包含错误。
- 20 行或更多:78% 包含错误。
JSLINT ...
- 识别不良风格、语法和语义;重构不良代码并用良好代码替换它。
圈复杂度...
- 通过代码的独立路径的数量。
- 此外,执行代码所需的单元测试数量也最少。
重复使用...
- 重复两次以上的代码应该是我们可以将其拉入其自身函数的候选对象。
人体测试...
- 复杂性归结为其他人阅读代码的难度。
- 代码审查流程:显示发现了 60-90% 的所有缺陷。
指标
(超出代码覆盖率和分支)
有两个测试指标需要关注:
- 仅因测试问题而非代码问题而导致的测试失败百分比。这种情况通常发生在模拟各种元素时。
- 错误修复或其他非重大更改需要更新单元测试的次数百分比。
目标是将这两个百分比都设为 0%。这表明每次测试失败都代表代码中存在实际问题,只有功能变更才需要更新测试。
什么导致代码难以测试?
- 当它紧密耦合时
- 隐藏或嵌入的依赖项
- 所需数据和数据库
- 大量的测试设置代码
设计模式
这些就是我们要关注的设计模式。开发人员需要编写可测试性的代码;他们使用的特定模式应该随着流程的推进而灵活变化。
测试驱动开发(TDD)
- 由内而外的视角(首先是单位级别)。
- 通过重构代码,测试领先开发一步。
问题:
- 可能会错过破坏模式。
- 编码前的集成测试是令人畏惧的。
- 在了解错误原因之前,几乎不可能编写重复错误的测试。
行为驱动开发(BDD)
- 由外而内的视角
- 根据需求和场景
- 用户的预期行为
边界测试
我们提倡测试任何第三方代码的“边界”。这个边界应该是我们“如何”使用他们的代码(希望他们对自己代码进行了良好的测试)。我们希望看到的是,当升级发生时,我们的实现(门控)可能在哪些地方发生了变化。
- 第三方用例:在当前状态下,很难升级。
-
测试我们在哪里以及如何使用它们的功能。
-
通过边界测试减少升级的痛苦,通过运行边界测试发现问题。
-
不需要测试第三方代码...他们会这么做。
结论
编写有效的单元测试和集成测试始于编写可测试的代码。在迈向持续开发/持续集成流水线时,需要进行可靠的测试来证明代码能够按预期运行。