所有语言的单元测试的一般规则

什么是单元测试?

单元测试是对代码的测试,以确保它执行它要执行的任务。它以尽可能最低的级别测试代码 - 类的各个方法。

什么是单位?

任何可以单独测试的离散代码模块。大多数时间类和他们的方法。该类通常被称为被测试类(CUT)或被测系统(SUT)

单元测试和集成测试之间的区别

单元测试是单独测试单个类的行为,完全不同于它的任何实际依赖性。集成测试是测试单个类及其一个或多个实际依赖项的行为。

SetUp 和 TearDown

在每个单元测试之前运行 SetUp 方法,在每次测试之后运行 TearDown。

通常,你可以在 SetUp 中添加所有先决条件步骤,并在 TearDown 中添加所有清理步骤。但是,如果每个测试都需要这些步骤,那么你只能使用这些方法。如果没有,则在安排部分的特定测试中采取这些步骤。

如何处理依赖项

很多时候,类具有其他类的依赖性来执行其方法。为了能够不依赖这些其他类,你必须假装这些。你可以自己创建这些类,也可以使用隔离或模型框架。隔离框架是一组代码,可以轻松创建虚假类。

假类

任何提供足以假装它是 CUT 所需依赖的功能的类。有两种类型的假货:Stubs 和 Mocks。

  • 存根:对测试的通过或失败没有影响的假,并且纯粹为了允许测试运行而存在。
  • 模拟:一种假冒,它跟踪 CUT 的行为,并根据该行为传递或未通过测试。

为什么单元测试?

1.单元测试会发现错误

当你编写一整套测试来定义给定类的预期行为时,会显示任何不符合预期的行为。

单元测试可以防止错误

进行一项引入错误的更改,你的测试可以在下次运行测试时将其显示出来。

3.单元测试可节省时间

编写单元测试有助于确保你的代码从一开始就按设计工作。单元测试定义了代码应该做什么,因此你不会花时间编写执行它不应该执行的操作的代码。没有人检查他们不相信有效的代码,你必须做一些事情让自己认为它有效。花时间编写单元测试。

4.单元测试让你高枕无忧

你可以运行所有这些测试,并知道你的代码按预期工作。了解代码的状态,它的工作原理,以及你可以毫无顾虑地更新和改进它是一件非常好的事情。

5.单元测试证明正确使用类

单元测试成为代码如何工作,预期执行的操作以及使用正在测试的代码的正确方法的简单示例。

单元测试的一般规则

1.对于单元测试的结构,请遵循 AAA 规则

安排:

设置要测试的东西。像变量,字段和属性一样,可以运行测试以及预期的结果。

行为: 实际上调用你正在测试的方法

断言:

调用测试框架以验证你的行为的结果是否符合预期。

2.单独测试一件事

所有类都应该单独测试。它们不应该依赖于模拟和存根以外的任何东西。他们不应该依赖于其他测试的结果。

3.首先写下简单的中间测试

你编写的第一个测试应该是最简单的测试。它们应该是基本上可以轻松地说明你要编写的功能的那些。然后,一旦这些测试通过,你应该开始编写更复杂的测试来测试代码的边缘和边界。

4.编写测试边缘的测试

一旦测试了基础知识并且你知道基本功能有效,就应该测试边缘。一组好的测试将探索给定方法可能发生的事情的外边缘。

例如:

  • 如果发生溢出会发生什么?
  • 如果值变为零或更低,该怎么办?
  • 如果他们去 MaxInt 或 MinInt 怎么办?
  • 如果你创建一个 361 度的圆弧怎么办?
  • 如果传递空字符串会发生什么?
  • 如果字符串大小为 2GB 会发生什么?

5.跨越边界测试

单元测试应测试给定边界的两侧。跨越边界是代码可能失败或以不可预测的方式执行的地方。

6.如果可以,测试整个光谱

如果它是实用的,请测试你的功能的整个可能性。如果它涉及枚举类型,请使用枚举中的每个项测试功能。测试每种可能性可能是不切实际的,但如果你能测试每种可能性,那就去做吧。

7.如果可能,请覆盖每个代码路径

这个也很有挑战性,但如果你的代码是为测试而设计的,并且你使用了代码覆盖工具,那么你可以确保代码的每一行都至少被单元测试覆盖一次。覆盖每个代码路径并不能保证没有任何错误,但它肯定会为你提供有关每行代码状态的宝贵信息。

8.编写揭示错误的测试,然后修复它

如果你发现了错误,请编写一个显示错误的测试。然后,你可以通过调试测试轻松修复错误。然后你有一个很好的回归测试,以确保如果 bug 因任何原因回来,你马上就会知道。当你在调试器中运行简单,直接的测试时,修复错误非常容易。

这方面的一个好处是你已经测试了你的测试。因为你已经看到测试失败,然后当你看到它通过时,你知道该测试是有效的,因为它已被证明可以正常工作。这使它成为更好的回归测试。

9.使每个测试彼此独立

测试不应该相互依赖。如果你的测试必须按特定顺序运行,则需要更改测试。

10.每次测试写一个断言

你应该为每个测试编写一个断言。如果你不能这样做,那么请对代码进行折射,以便使用 SetUp 和 TearDown 事件正确创建环境,以便可以单独运行每个测试。

11.清楚地命名你的测试。不要害怕长名

由于每个测试都在执行一个断言,因此每个测试最终都非常具体。因此,不要害怕使用长而完整的测试名称。

一个完整的长名称可以让你立即知道测试失败的原因以及测试尝试的确切内容。

长,明确命名的测试也可以记录你的测试。名为 DividedByZeroShouldThrowException 的测试准确记录了当你尝试除以零时代码所执行的操作。

12.测试实际引发的每个引发的异常

如果你的代码引发了异常,那么编写一个测试来确保你实际引发的每个异常都应该被引发。

13.避免使用 CheckTrue 或 Assert.IsTrue

避免检查布尔条件。例如,如果检查两个东西是否与 CheckTrue 或 Assert.IsTrue 相等,请改用 CheckEquals 或 Assert.IsEqual。为什么?因为这:

CheckTrue(预期,实际)这将报告类似:“某些测试失败:预期为 True 但实际结果为 False。”

这并没有告诉你任何事情。

CheckEquals(预期,实际)

这将告诉你类似的事情:“一些测试失败:预期 7 但实际结果为 3”。

当你的预期值实际上是布尔条件时,仅使用 CheckTrue 或 Assert.IsTrue。

14.不断运行测试

在编写代码时运行测试。你的测试应该快速运行,使你能够在经过微小更改后运行它们。如果你无法在正常开发过程中运行测试,那么就会出现问题。单元测试应该几乎立即运行。如果不是,那可能是因为你没有孤立地运行它们。

15.将测试作为每个自动构建的一部分运行

正如你在开发过程中应该运行测试一样,它们也应该是你持续集成过程中不可或缺的一部分。测试失败应该意味着你的构建被破坏了。不要让失败的测试徘徊。将其视为构建失败并立即修复。