单元测试中“单元”下的理解


9

据我理论上理解,“单位”下的人是指方法(在OOP中)。但是在实践中,孤立地验证某些方法的测试是非常脆弱的行为测试(不是验证结果,而是验证某些依赖方法的事实)。因此,我看到很多人从单位上了解一小组紧密相关的课程。在这种情况下,仅对外部依赖项进行了模拟/存根,对于内部单元内部实现中的依赖项,则使用该方法。在这种情况下,存在更多的状态,有意义的(根据规范)并且不是那么脆弱的测试。因此,问题是您对这些方法有何看法?将第二种方法称为单元测试是否有效?或者它是某种低级别的集成测试?

如果您发现通过这些测试方法之一应用TDD的一些特定注意事项,我将感谢您的想法。

Answers:


11

TDD最初来自敏捷运动,在这种情况下,测试是预先编写的,以确保在给定测试规范中现已明确定义的规范的情况下,您编写的代码保持正确。它也是重构的一个非常重要的方面,因为当您修改代码时,您可以依靠测试来证明您没有更改代码的行为。

然后,人们开始使用工具,以为他们知道有关您的代码的信息,然后可以生成测试存根,以帮助您编写单元测试,我认为这一切都是错误的。

测试存根是由一台不知道您在做什么的计算机生成的,它只是无意识地为每种方法生成一个存根,因为这就是要执行的操作。这意味着您对每种方法都有一个测试用例,而不管该方法的复杂性或是否适合单独测试。

这是在TDD方法错误的一端进行测试的。在TDD中,您应该弄清楚代码要做什么,然后生成实现此目的的代码。这是自我实现的,因为您最终要编写测试来证明代码能够执行代码的工作,而不是代码应该执行的工作。与基于方法的测试存根的自动生成相结合,您几乎浪费了时间来证明代码的每个细微方面,当将所有小片段放在一起时,很容易被证明是错误的。

当福勒在书中描述测试时,他提到使用自己的主要方法测试每个类。他对此进行了改进,但是概念仍然相同-您测试整个类,使其整体上起作用,所有测试捆绑在一起以证明所有这些方法的相互作用,以便可以按定义的期望重用该类。

我认为测试工具包给我们带来了损害,使我们误以为该工具包是在现实中做事的唯一方法,而实际上,您需要更多地思考以从代码中获得最佳结果。将测试代码盲目地放到测试存根中只是意味着您无论如何都必须在集成测试中重复工作(如果要这样做,为什么不完全跳过现在冗余的单元测试阶段)。这也意味着人们浪费大量时间试图获得100%的测试覆盖率,并花费大量时间来创建大量的模拟代码和数据,而这些时间本来可以使代码更易于集成测试(例如,如果您拥有那么多代码)数据依赖性,单元测试可能不是最佳选择)

最后,基于方法的单元测试的脆弱性仅显示了问题。重构被设计为与单元测试一起使用,如果您的测试由于重构而一直中断,那么整个方法就会出现严重问题。重构喜欢创建和删除方法,因此显然基于盲法的基于测试的测试方法并不是最初打算的。

我毫不怀疑,许多方法都会为它们编写测试,应该测试类的所有公共方法,但是您不能逃避将它们作为单个测试用例的一部分进行测试的概念。例如,如果我有一个set和一个get方法,我可以编写将数据放入其中并检查内部成员设置是否正确的测试,或者可以使用每个放入一些数据然后再次将其取出来查看是否还是一样,不会乱码。这是在测试类,而不是孤立地测试每个方法。如果setter依赖于helper私有方法,那很好-您不需要模拟私有方法来确保setter正常工作,而不是测试整个类。

我认为宗教正在进入这个主题,因此您会看到现在被称为“行为驱动”和“测试驱动”开发的分裂-单元测试的原始概念是行为驱动开发。


10

一个单元通常被定义为“ 应用程序的最小可测试部分 ”。是的,这通常是一种方法。是的,这意味着您不应该测试依赖方法的结果,而应该仅仅调用该方法(然后,如果可能的话,只测试一次,而不是在对该方法的每次测试中)。

您称此为脆弱。我认为这是不正确的。脆弱的测试是那些在不相关的代码的微小变化下破裂的测试。也就是说,那些依赖与所测试功能无关的代码的代码。

但是,我想您真正要说的是,测试不依赖任何方法的方法并不彻底。在这一点上,我同意。您还需要进行集成测试,以确保将代码单元正确地挂钩在一起以构成应用程序。

这正是行为驱动的开发,特别是验收测试驱动的开发要解决的问题。但这并不能消除对单元测试/测试驱动开发的需求。它只是对它的补充。


我真的很想说行为测试很脆弱。在代码库更改期间,它们通常变为假阴性。状态测试的情况较少(但是状态测试很少用于单元测试)
SiberianGuy

@Idsa:我对您的定义有些迷惑。行为测试是集成测试,按照指定的测试行为。阅读您的原始问题,看来当您说状态测试时,您的意思是相同的。
pdr

所谓状态,是指验证状态,某些功能的结果的测试;通过行为,我的意思是说测试不是验证结果,而是验证某些功能的事实
SiberianGuy

@Idsa:在那种情况下,我完全不同意。您所说的状态测试,我称之为集成。你所说的行为,我叫单位。就其定义而言,集成测试更加脆弱。Google“集成测试单元脆弱”,您会发现我并不孤单。
pdr

有关于测试的文章日志,但是其中哪些与您意见一致?
SiberianGuy

2

顾名思义,您在每次测试中都要测试一个原子主题。这样的主题通常是单一方法。多个测试可以测试相同的方法,以覆盖幸福的道路,可能的错误等。您正在测试行为,而不是内部机制。因此,单元测试实际上是关于测试类的公共接口,即特定方法。

在单元测试中,需要对一种方法进行隔离测试,即通过对所有依赖项加桩/模拟/伪造。否则,测试具有“实际”依赖关系的单元将使其成为集成测试。两种类型的测试都有时间和地点。单元测试可确保单个主题独立运行。集成测试可确保“真实”主题能够正确协同工作。


1
不完全是,一个单元是任何孤立的主题,只是因为自动化工具更喜欢对待一种方法,因为这并非如此,也不是使其成为最佳方法。“隔离”是这里的关键。即使您测试方法,也应该测试私有方法。
gbjbaanb 2013年

1

我的经验法则:最小的代码单元,仍然足够复杂以包含错误。

这是方法,类还是子系统取决于特定的代码,无法给出一般规则。

例如,它不提供任何值来单独测试简单的getter / setter方法或仅调用另一个方法的包装器方法。如果整个类只是一个瘦包装器或适配器,那么即使是整个类也可能过于简单而难以测试。如果唯一要测试的是是否调用了模拟程序中的方法,则要测试的代码将变得稀薄。

在其他情况下,单个方法可能会执行一些复杂的计算,这对于隔离测试很有用。

在许多情况下,复杂的部分不是单独的类,而是类之间的集成。因此,您可以一次测试两个或多个类。有人会说这不是单元测试,而是集成测试,但不要介意术语:您应该测试复杂性在哪里,这些测试应该成为测试套件的一部分。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.