何时测试不是单元测试?

58

我正在寻找类似以下规则的内容:

如果一个测试符合以下条件之一,它就不是一个单元测试:

  • 与数据库通信
  • 无法与其他测试同时运行
  • 使用“环境”(如注册表或文件系统)

还有哪些条件呢?


除了已经说的之外,请检查:这个问题 - Finglas
12
Roy Osherove对单元测试的定义:仅在内存中运行,运行速度快,可重复,不涉及任何外部资源。 - Peter Gfader
8个回答

70

请查看 Michael Feathers 的定义

如果以下情况发生,那么一个测试就不是单元测试:

  • 需要读写数据库
  • 需要进行网络通信
  • 需要涉及文件系统
  • 无法与其他单元测试同时运行
  • 需要对环境进行特殊操作(例如编辑配置文件)才能运行

15
当然不是。 涵盖多个层次(例如业务逻辑、视图和控制器)的测试也不被认为是单元测试。 - Malte Clasen
7
我认为列出特征清单并不是定义概念的有效方法。这会将理解降低到一些需要记住的规则和琐事。虽然初学者自然会渴望规则——我也是其中之一,而且我在单位测试方面是一个初学者——但从长远来看,更有效的方法是集中精力于概念本身,了解它的重要性,然后实验、思考它如何适应你自己的环境。因此,当初学者寻求规则时,关心他们的专家首先应该解释这个想法,然后列出一些例子。 :) - The Dag
1
单元测试的特征取决于我们要测试的代码单元。如果您为数据库插入/选择编写DaoTest,则实际上正在对映射进行单元测试(对于ORM而言)或相对于表结构的预期映射。我们实际上并没有在测试数据库或层,而是只测试我们在DAO中编写的代码单元。因此,如果它涉及数据库,那么一个通用语句是无效的。 - DarkKnight
2
-1 这个回答只是“教条主义”。更有用的定义应该引导开发人员创建好的、有用的自动化测试,而不仅仅是试图符合任意标准。特别是关于触及文件系统的一点,鼓励开发人员尝试将文件系统模拟掉,这在实践中通常非常困难;此外,IO代码往往使用低级API,因此这段代码具有很强的实现特定性;确实让测试触及(本地)文件系统会更好。 - Rogério
1
@Rogério 重点是单元测试与其他任何东西都是独立的。如果文件改变位置怎么办?模拟数据并不可怕。 - sparkyShorts
显示剩余2条评论

36

如果测试不是在测试一个单元,那它就不是一个单元测试。

事实上,这就是全部内容。

“单元”在单元测试中的概念并没有明确定义,实际上,迄今我找到的最好的定义并不算是一个定义,因为它是循环的:单元测试中的一个单元是可以在孤立的情况下进行测试的最小可能的东西。

这给你两个检查点:它是否在孤立的情况下进行测试?它是最小可能的东西吗?

请注意,这两个都是依赖于上下文的。在某些情况下(例如整个对象),可能是最小可能的东西,但在另一种情况下,仅仅是单个方法的一小部分。在某些情况下,孤立的定义也会不同(例如在内存管理语言中,您永远无法与垃圾收集器隔绝开来,大多数情况下这并不重要,但有时候可能并非如此)。


+1:非常好。肯定和卡尔引用的列表一样有用。 - Martin Ba
1
+1:这种理解应该是我们彼此分享的,而不是列表和规则 - 因为(a)它们充其量只是典型的,而不是普遍的,(b)理解概念是唯一能让你知道何时打破规则的东西。 - The Dag

7

这是一个比较难的问题...

对我来说,单元测试是验证独立某一特定逻辑的方式。也就是说,我会将某些逻辑从整体中提取出来(如果需要通过模拟依赖项),然后仅测试这个逻辑——一个单元(整体的一部分)——以探索不同种类的可能控制流。

但另一方面...我们能否总是100%确定正确或错误??不想陷入哲学,但正如Michael在他的帖子中所说:

执行这些操作的测试并不是坏事。 它们通常值得编写,并且可以在单元测试工具中编写。 然而,重要的是要能够将它们与真正的单元测试分开, 这样我们就可以保持一组测试,在每次进行更改时都可以快速运行。

那么为什么我不能编写一个单元测试,通过访问测试文件夹中的某个虚拟文件(例如MS测试允许使用DeploymentItem)来验证解析xls文件的逻辑呢?

当然——如上所述——我们应该将这些测试与其他测试分开(可能在JUnit的单独测试套件中)。但我认为,如果他们感到舒适,一个人也应该编写这些测试...当然又要记住,单元测试只应该测试隔离的一部分。

在我看来,最重要的是这些测试运行快速,并且不要花费太长时间,以便可以重复运行和频繁运行。


吹毛求疵:在您的xls解析示例中,最好通过重定向IO而不是从文件系统中读取输入,并且如果程序允许,则输出到stdio。或者使用某种基于内存的文件系统。您可以考虑这是真正隔离的,它将允许并行运行测试。测试隔离不是“真正”的单元测试的唯一标准,它还在更高级别的测试中提供了重大优势。 - Merlyn Morgan-Graham
1
不仅仅是速度的问题...您不应该需要进行任何配置工作,比如设置RDBMS来运行单元测试。例如,从代码库检出新代码 -> 运行测试 -> 所有测试通过。 - hijarian

7

此代码没有断言,并且不希望抛出异常。


2
它默默地通过了。在某些IDE中,您可以调整默认的单元测试模板,将Assert.Fail()或等效内容包含在测试方法的最后一行中。这样,它就会“默认失败”。 - pgb
5
不能完全同意。您可以进行一些测试,只测试某些代码是否能够运行而不抛出异常。虽然这并不理想,但有时总比什么都不做要好。您可以说单元测试中隐含了一个断言,即被测试的单元不会抛出异常。 - Stefan Steinegger
你可以在没有Assert的情况下进行单元测试。例如,在MSTest中,您可以编写一个单元测试来检查被测试系统是否抛出了正确的异常。 - Spock
只是稍微修改了一下,以包括期望异常的情况。 - pgb
@pgb,这个定义是从哪里来的?断言和异常与单元的概念有什么关系?如果符合Michael Feathers和Roy Osherove对单元测试的定义,那么你如何称呼一个包含断言或期望异常的xunit测试? - aro_tech
显示剩余6条评论

6

当测试同时测试多个事情时(即测试两个事物如何协同工作),它不是单元测试,而是集成测试。

好的单元测试清单:

  • 它们是自动化的
  • 它们是可重复的
  • 它们易于实现
  • 一旦编写,它们将保留以供将来使用
  • 任何人都可以运行它们
  • 只需按下一个按钮即可运行它们
  • 它们运行速度快

更多最佳实践(没有特定的重要性顺序):

  • 测试应该与集成测试(速度较慢)分开,以便尽可能频繁地运行
  • 它们不应包含太多逻辑(最好没有控制结构)
  • 每个测试应该只测试一件事情(因此,它们应该只包含一个断言)
  • 断言中使用的期望值应该是硬编码的,而不是在测试运行时计算的
  • 外部依赖项(文件系统、时间、内存等)应该被替换为存根
  • 测试应该在测试关闭时重新创建初始状态
  • 在断言中,最好使用“包含…”策略,而不是“严格相等…”策略(即,我们期望集合中有某些值,在字符串中有某些字符等)

这是我从Roy Osherove的书《The Art of Unit Testing》中提取出来的知识的一部分。


1
我认为“应该用存根替换外部依赖(文件系统、时间、内存等)”不仅仅是最佳实践;-) - Peter Gfader
1
使用模拟对象时,您可以遵循“包含”策略,检查是否调用了预期的方法,但忽略了其他方法的调用。严格的模拟对象与此相反(当出现意外的方法调用时,它们会抛出断言),并使测试更加脆弱。当然,如果您要模拟的接口重要性在于仅调用某个方法x次,或按特定顺序调用某些方法,则可能需要明确断言。该断言将放置在其自己的单独测试中。 - Merlyn Morgan-Graham

2
在多个可能失败的单元之间实施测试将不是单元测试。

1

复杂的问题。

假设我要编写一些业务逻辑,所有业务逻辑都需要通过某种形式的数据访问层(DAL)来访问数据。

为了测试目的,我模拟了DAL单元(通过创建“mockingbirds”)。

但是这些mockingbirds当然也是它们自己的附加单元。因此,即使使用mocks,当我想对业务逻辑模块进行单元测试时,似乎仍然会违反“没有其他单元参与”的想法。

当然,通常人们知道,“为DAL创建mockingbirds”可能会使您的测试本身无效,因为您的mockingbird在某些特定方面偏离了DAL。

结论:对于任何以任何方式依赖于任何类型的DAL的业务模块,都不可能进行“真正的单元测试”,是吗?

推论:唯一可能进行“真正的”单元测试的是DAL本身,是吗?

推论的推论:鉴于“DAL”通常是ORM或某个DBMS的DML,而且这些产品通常被认为是“经过验证的技术”,那么进行任何单元测试的附加价值是什么,是吗?


有趣的想法 :-) .... 我不会说这样的话:"在业务模块上进行'真正的单元测试'是完全不可能的....而且我也不会说数据访问层(DAL)可以进行单元测试...." - Peter Gfader
你必须始终假设(即确保)模拟接口以获得你从实现中期望的结果,以便模拟和真实对象之间存在关联。因此,我不同意你的推论:当然,如果DAL是一个你没有自己编写的ORM工具,可能很难精确地模拟它的工作方式。但是,通过正确地模拟它,仍然可以测试业务模块,并通过模拟业务模块的接口,可以轻松地测试链条中的任何“后续”内容。 - Tomas Aschan
我也在开发DAL层,最糟糕的部分是单元测试所需的时间比编码还要长。有些东西可以测试,但有些则不行,除非你假设数据。有些测试会间歇性地通过(例如与IO相关的内容可能会产生意外结果)。另一个问题是我不能同时运行所有测试用例,因为UTCs依赖于状态。 - DPD

0

在确定一个测试是不是单元测试之后,下一个问题是,它是一个好的单元测试吗?


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