什么是自动测试?
在本单元中,你将了解自动测试的优点以及你可执行的测试类型。 你还将学习如何进行良好的测试,并了解可供你使用的一些测试工具。
什么是自动测试?
自动测试使用软件来执行代码,并将实际结果与预期结果进行比较。 将该测试与探索或手动测试进行比较;在手动测试中,人员通常按照测试计划中的说明验证软件是否按预期工作。
手动测试有自己的优势。 但随着基本代码大小增大,手动测试所有功能(包括边缘用例)变得重复、繁琐,并且容易出错。 自动测试可有助于消除一些负担,并使手动测试人员能够专注于他们最擅长的领域:确保用户在使用你的软件时能拥有不错的体验。
测试金字塔
当我们考虑自动测试时,通常将测试分为多层。 Mike Cohn 在他的《敏捷软件开发》(Succeeding with Agile) 一书中提出了这一概念,称为“测试金字塔”。
尽管这是 Cohn 的模型的简化版本,但此概念说明你特别专注于编写测试,以验证软件的功能级别(金字塔中的标注 1),如函数、类和方法。 当功能组合在一起时,你的注意力会逐渐减少,例如在用户界面 (UI) 层(金字塔中的标注 2)。 其思路是,如果你可验证每个较低级别的组件在隔离时按预期正常工作,那么较高级别的测试只需验证多个组件是否一起工作来获得预期的结果。
我何时该编写测试?
答案主要取决于你对编写测试的需求和经验。
无论何时开始为已编写和部署的代码添加测试都不算晚。 这对经常中断或需要测试团队最大努力的功能来说尤其如此。
当你将测试与持续集成和持续交付管道关联时,你将听到两个概念:“持续测试”和“左移”。
持续测试是指在开发过程的早期运行测试,每次更改经过管道时便会运行测试。 左移是指在开发过程中提前考虑软件质量和测试。
例如,开发人员经常在开发功能时添加测试用例,并在将更改提交到管道之前运行整套测试。 此方法有助于确保他们生成的功能按预期运行,并且不会中断现有功能。
下面是一个简短的视频,其中 Microsoft 云大使 Abel Wang 介绍了如何确保在 DevOps 计划中维护质量。
咨询 Abel
左移通常要求测试人员参与设计过程,甚至是在为功能编写任何代码之前就参与进来。 将它与“移交”模型进行比较;在移交中,仅在设计和编写软件后,才向测试团队提供新功能进行测试。 在流程后期发现的 bug 可能会影响团队的交付计划,在开发人员最初生成该功能几周甚至几个月后,可能会发现 bug。
弊端
使用自动测试时有一个弊端。 尽管自动测试使测试人员能够专注于验证最终用户体验,但开发人员可能需要将更多时间专用于编写和维护测试代码。
但是,自动测试的重点是帮助确保测试人员仅接收最高质量的代码和已被证明可按预期工作的代码。 因此,开发人员可通过处理更少的 bug 或避免代码重写来节省部分时间,因为他们最初没有考虑过边缘案例。
其他优点
文档和更轻松重构代码的功能是自动测试的另外两个优点。
文档
手动测试计划可充当一种文档类型,记录软件的行为方式及存在某些功能的原因。
自动测试也可实现这一目的。 自动测试代码经常使用用户可读格式。 提供的输入集表示用户可能输入的值。 每个关联的输出指定用户应期望的结果。
事实上,许多开发人员通过在实现新功能之前编写测试代码,来遵循测试驱动开发 (TDD) 方法。 其思路是编写一组通常称为“规范”的测试,最初会失败。 然后,开发人员增量编写实现此功能的代码,直到所有测试都通过。 不仅规范记录要求,而且 TDD 过程帮助确保只编写必要数量的代码来实现此功能。
重构
假设你有一个大型基本代码,你想重构它来使某些部分更快运行。 如何知道重构工作不会导致应用程序的某些部分中断?
自动测试充当一种协定。 也就是说,你指定输入和预期结果。 当你有一组通过测试时,就可以更好地试验和重构代码。 进行更改时,只需运行测试,并验证它们是否继续通过。 达到了重构目标后,可以将更改提交到生成管道,这样每个人都可以获益,并降低某些内容中断的风险。
有哪些类型的自动测试?
有多种类型的自动测试。 每个测试实现不同的目的。 例如,你可能运行安全测试来帮助验证只有经过授权的用户才能访问软件或其中一项功能。
当我们提到持续集成和生成管道时,通常是指开发测试。 开发测试是指你在将应用程序部署到测试或生产环境之前可运行的测试。
例如,lint 测试(一种静态代码分析形式)会检查源代码,确定它是否符合团队的风格指南。 以一致的方式进行格式化的代码便于每个人读取和维护。
在本模块中,你将使用单元测试和代码覆盖率测试。
单元测试将验证程序或库的最基本组件,例如单个函数或方法。 指定一个或多个输入以及预期结果。 测试运行程序会执行每个测试,并查看实际结果与预期结果是否匹配。
例如,假设你有一个函数,它执行包含除法的算术运算。 你可指定几个希望用户输入的值,以及边缘用例值(例如 0 和 -1)。 如果某个输入生成错误或异常,则可验证该函数是否也生成相同的错误。
代码覆盖率测试计算单元测试所覆盖的代码的百分比。 代码覆盖率测试可在代码中包含条件分支,来确保覆盖函数。
代码覆盖率百分比越高,你越能确信今后不会发现未经过全面测试的代码中的 bug。 不需要达到 100% 的代码覆盖率。 事实上,刚开始时,你可能会发现百分比较低。但这为你提供了一个起点,你可就此添加其他测试来涵盖有问题或经常使用的代码。
使单元测试保持隔离
在了解单元测试时,你可能会听说模拟、存根和依赖项注入等术语。
回顾一下,单元测试应验证单个函数或方法,而不是多个组件的交互方式。 但是,如果你有一个调用数据库或 Web 服务器的函数,你将如何处理它呢?
对外部服务的调用不仅会中断隔离,它也可能降低速度。 如果该数据库或 Web 服务器发生故障或不可用,则调用也会中断测试运行。
通过使用模拟和依赖关系注入等方法,你可创建模仿此外部功能的组件。 稍后会在本模块中看到一个示例。
稍后,你可运行集成测试来验证应用程序是否在实际数据库或 Web 服务器上正常运行。
什么造就了良好测试?
当你在编写自己的测试和读取其他人编写的测试方面拥有经验时,你将能够更好地识别一个好的测试。 下面是一些入门指南:
- 不要为了测试而测试:你的测试要实现的目的不该仅是消除一个清单项。 编写测试来验证关键代码是否按预期运行且不会中断现有功能。
- 使测试简短:测试应尽快完成,特别是在开发阶段和生成阶段测试时。 如果在每个更改通过管道移动时运行测试,你不希望它们成为瓶颈。
- 确保测试是可重复的:测试运行每次都应生成相同的结果,无论你是在自己的计算机、同事的计算机上还是在生成管道中运行它们。
- 使测试保持专注:常见的误解是测试旨在涵盖其他人编写的代码。 通常,测试应只涉及你的代码。 例如,如果你在项目中使用开源图形库,则无需测试该库。
- 选择正确的粒度:例如,如果要执行单元测试,则单个测试不应组合或测试多个函数或方法。 单独测试每个函数,稍后编写集成测试来验证多个组件是否正常交互。
哪些测试工具类型可供使用?
使用哪种测试工具取决于你正在生成的应用程序类型和要执行的测试类型。 例如,可使用 Selenium 对多种类型的 Web 浏览器和操作系统执行 UI 测试。
无论你的应用程序使用何种语言编写,都有很多测试工具供你使用。
例如,对于 Java 应用程序,可选择 Checkstyle 来执行 lint 测试,选择 JUnit 来执行单元测试。
在本模块中,我们将使用 NUnit 进行单元测试,因为它在 .NET 社区中很受欢迎。