如何进行单元测试?

7

基本上,我有两个主要问题:

  • 究竟应该对什么进行单元测试?
  • 如何做到这一点?

问题在于,我有几个依赖数据库连接和/或通信应用程序的应用程序,这意味着大多数测试用例都是集成测试(或者我认为是这样)。

大多数类本身都相当简单,但实现通信协议的那些类,这些类将有助于自动化测试,似乎很难适应“单元测试”模型。

另一个例子。 我开发了一个具有多线程支持的消费者/生产者模式的管道结构。 当线程读取管道并发现它为空时,它会阻塞,直到写入器将内容写入管道。 我应该使用单元测试来测试该类吗?

你如何决定要进行单元测试的内容?

编辑:我的意思是编写自动化单元测试的单元测试。

5个回答

4
你需要对你的代码单元进行单元测试,而真正的问题在于什么构成了一个“单元”?
在面向对象的环境中,一个“单元”就是一个类。一个类因为其对象的状态不同而表现出不同的行为,所以仅测试一个方法将无法得到最完整的结果。
首先,你需要确定这个类的不变量。也就是说,这些东西将始终适用于该类的所有实例。例如,在分数类中,一个不变量可能是分母!=0.
接下来,你需要确定每个方法的合同,即方法的前置条件和后置条件。
然后,你为可能出现的每个条件编写测试。因此,对于一个单独的类,你可能会得到许多测试方法,以测试每个方法可能遇到的各种条件。在每个测试中,你确保类的不变量保持不变,并且方法的合同从未被违反。
在某些情况下,例如你提供的示例,可能需要创建其他对象来测试类的条件。在这种情况下,你可以使用模拟对象。

那么,即使您需要模拟其他对象或模拟外部事件或设备,它也可以被视为单元测试,并且应编写相应的测试吗? - Jorge Córdoba
@Jorge Corboda 是的,我也这么认为。许多像在容器中运行的代码等环境本身就很难进行单元测试。因此,为测试创建模拟对象被认为是一种好的实践。但重要的是,测试代码应该能够独立运行。 - Vincent Ramdhanie
尽管从技术上讲,任何具有超出被测试单元的依赖关系(数据库、文件系统等)的代码测试都是集成测试。然而,这两个术语可以互换使用,并且经常被互换使用。 - ZombieSheep

1

你应该将基础设施问题(例如从数据库检索数据的代码,执行文件 I/O 的代码等)抽象出来,以便可以在单元测试应用程序时存根/模拟这些部分。然后,您将能够针对基础设施代码编写有针对性/具体的测试以进行测试。

您将发现自己正在创建更多接口(以创建应用程序内的功能),并需要使用更好的面向对象原则(即 SOLID)以开发更易于测试的应用程序。

一年前我也曾经历过你所经历的困境。真正帮助我度过难关的是一本书(加上一些实践):《单元测试的艺术》Roy Osherove 著


1

单元测试测试单元(即方法或函数)独立运行,在一个专用的、受控制的环境中进行。每个单元测试通过实例化仅需执行一个测试所需要的类,将它们置于已知状态下,然后调用待测试的方法并验证结果。这种验证是通过对方法行为的断言来完成的(而不是对其实现的断言)。

对行为而不是实现进行验证很重要,因为这样可以在不破坏单元测试的情况下修改实现,并将单元测试用作修改的安全保障。

所有语言都至少有一个单元测试框架,其作用是执行单元测试。编写单元测试有两种方式:先测试后编码或先编码后测试。

先测试后编码也被称为测试驱动开发。基本上有三个步骤:

  1. 编写一个失败的测试
  2. 仅编写足够的代码使其通过
  3. 重构代码以清理代码(去除重复...)

TDD的支持者声称这会导致可测试的代码,而在方法执行多个操作时,事后编写单元测试可能会很困难。建议遵循单一职责原则。

关于管道结构和通信协议示例,一些指南表示如果

  • 它与数据库交互
  • 它跨网络通信
  • 它触及文件系统
  • ...

当线程读取管道并发现为空时,它会阻塞直到写入器将数据写入管道。我应该使用单元测试来测试该类吗?

我会测试该类,但不会测试阻塞读取方法,因为我认为它是从操作系统的read()函数的阻塞调用构建的。


TDD有一个非常重要的第四步 - “在重构后确保测试仍然通过”。这是显而易见的,但有时会被忽视。 - ZombieSheep
同意这很重要,但它属于重构的一部分,而不是 TDD。TDD 周期是“红灯、绿灯、重构”。 - philant

0

单元测试是测试整个单元在更改后是否与之前相同,并且改进已经在更改中进行。如果您正在更改模块的窗口,则测试该模块。这与系统测试相比,系统测试是测试每个模块。如果您的更改影响许多模块(不确定您的系统设置如何),则应进行系统测试。


嗯,我的错,我是指自动化单元测试...就像你实际编写的那种 :) - Jorge Córdoba

0

这里有另一种思考方式 - 特别是如果你不专注于面向对象的代码,而是更多地关注函数式或过程式代码。

理想情况下,单元测试应该覆盖代码中的每个路径。这是你看到代码中每个路径按预期和需要工作的机会。如果你正在实践测试驱动开发,那么就意味着每个东西都要进行测试。

我认为查看这篇博客文章将有助于澄清:https://earnestengineer.blogspot.com/2018/03/unit-testing-what-to-test.html


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接