18

这似乎引发了关于另一个问题的一些对话,我认为值得转入自己的问题。

DRY 原则似乎是我们解决维护问题的首选武器,但测试代码的维护呢?是否适用相同的经验法则?

开发人员测试社区中的一些强烈声音认为设置和拆卸是有害的,应该避免......仅举几例:

事实上,xUnit.net 已经完全出于这个原因将它们从框架中删除了(尽管有一些方法可以绕过这个自我强加的限制)。

你有什么经验吗?设置/拆卸是否会伤害或帮助测试可维护性?

更新:像 JUnit4 或 TestNG(@BeforeClass、@BeforeGroups 等)中可用的更细粒度的结构会有所作为吗?

4

9 回答 9

11

setup 和 teardown 方法的大部分(如果不是全部)有效用途可以写成工厂方法,这允许 DRY 而不会陷入似乎受到 setup/teardown 范式困扰的问题。

如果您正在实施拆解,通常这意味着您不是在进行单元测试,而是在进行集成测试。很多人以此为理由不进行拆解,但 IMO 应该同时进行集成和单元测试。我个人会将它们分成单独的程序集,但我认为一个好的测试框架应该能够支持这两种类型的测试。并非所有好的测试都是单元测试。

但是,通过设置,您需要在实际运行测试之前执行操作似乎有很多原因。例如,构建对象状态以准备测试(例如设置依赖注入框架)。这是设置的正当理由,但可以通过工厂轻松完成。

此外,类和方法级别的设置/拆卸之间存在区别。在考虑您要做什么时,需要牢记这一点。

我在使用 setup/teardown 范例时遇到的最大问题是我的测试并不总是遵循相同的模式。这让我转而使用工厂模式,这让我可以在具有 DRY 的同时具有可读性,并且不会让其他开发人员感到困惑。走工厂路线,我已经可以吃蛋糕了。

于 2009-07-06T14:41:08.797 回答
2

它们确实对我们的测试可维护性有所帮助。我们的“单元”测试实际上是完整的端到端集成测试,它写入数据库并检查结果。不是我的错,当我来到这里时他们就是这样,我正在努力改变事情。

无论如何,如果一个测试失败,它会继续下一个测试,尝试从数据库中的第一个测试输入相同的用户,违反唯一性约束,并且失败只是从那里级联。将用户创建/删除移动到 [Fixture][SetUp|TearDown] 方法中,我们可以看到一个失败的测试,但一切都没有搞定,这让我的生活变得更轻松,也更轻松。

于 2009-07-06T14:42:00.230 回答
2

我认为 DRY 原则适用于测试和代码一样多,但是它的应用是不同的。在代码中,你会花更多的时间在代码的两个不同部分做同样的事情。在测试中需要这样做(做很多相同的设置)当然是一种气味,但解决方案不一定是将重复因素分解到设置方法中。它可能使状态更容易在类本身中设置或隔离被测代码,因此它不太依赖于这个数量的状态是有意义的。

鉴于每次测试只测试一件事的总体目标,在某些情况下(例如创建某种类型的对象)确实不可能避免一遍又一遍地做很多相同的事情。如果你发现你有很多,可能值得重新考虑测试方法,例如引入参数化测试等。

我认为 setup 和 teardown 应该主要用于建立环境(例如注入以使环境成为测试环境而不是生产环境),并且不应包含作为测试组成部分的步骤。

于 2009-07-06T14:43:54.343 回答
2

我同意 Joseph 所说的一切,尤其是关于 tearDown 是编写集成测试的标志的部分(99% 的时间是我使用它的目的),但除此之外,我还要说使用设置的设置是一个很好的指标,可以很好地指示测试何时应在逻辑上分组在一起以及何时应将它们拆分为多个测试类。

在将测试应用于遗留代码时,我对大型设置方法没有任何问题,但设置应该对套件中的每个测试都是通用的。当您发现自己的 setup 方法确实在执行多个设置时,是时候将您的测试拆分为多个案例了。

按照“测试驱动”中的示例,设置方法来自删除测试用例中的重复。

于 2009-07-06T15:03:12.753 回答
1

我在 Java 和 Python 中经常使用 setup,经常设置协作者(真实的或测试的,取决于)。如果被测对象没有构造函数或只有协作者作为构造函数,我将创建该对象。对于一个简单的值类,我通常不会理会它们。

我在 Java 中很少使用拆解。在 Python 中,它被更频繁地使用,因为我更有可能更改全局状态(特别是猴子修补模块以使这些模块的用户处于测试状态)。在这种情况下,我想要一个拆解,如果测试失败,它会保证被调用。

集成测试和功能测试(通常使用 xunit 框架)更有可能需要设置和拆卸。

要记住的一点是考虑固定装置,而不仅仅是 DRY。

于 2009-07-06T15:06:22.763 回答
1

我对测试设置和拆卸方法本身没有问题。

我的问题是,如果您有测试设置和拆卸方法,这意味着每个测试都重复使用相同的测试对象。这是一个潜在的错误向量,就像您忘记清理测试之间的某些状态元素一样,您的测试结果可能变得依赖于顺序。我们真正想要的是不共享任何状态的测试。

xUnit.Net 摆脱了 setup/teardown,因为它为每个运行的测试创建一个新对象。本质上,constructor变成了setup方法,finalizer变成了teardown方法。测试之间没有(对象级)状态,消除了这个潜在的错误向量。

我编写的大多数测试都有一些设置,即使它只是创建我需要的模拟并将被测试的对象连接到模拟。他们不做的是在测试之间共享任何状态。拆解只是确保我不分享那种状态。

于 2009-07-07T03:33:37.000 回答
0

我没有时间阅读您发布的两篇文章,但我特别喜欢这条评论:

每个测试都被迫对其需要运行的内容进行初始化。

设置和拆除是方便的方法——它们不应该尝试做更多的事情,而不仅仅是使用它的默认构造函数初始化一个类等。五个测试类中的三个测试需要的公共代码不应该出现在那里——三个测试中的每一个应该直接调用此代码。这也可以防止测试因为您更改了常见的初始化例程而相互影响并破坏了一堆测试。主要问题是这将在所有测试之前调用 - 而不仅仅是特定测试。大多数测试应该很简单,而更复杂的测试将需要初始化代码,但是当您不必跟踪设置中的复杂初始化和拆除中的复杂破坏时,更容易看到简单测试的简单性。思考测试实际上应该完成什么。

于 2009-07-06T14:42:49.400 回答
0

就个人而言,我发现 setup 和 teardown 并不总是邪恶的,而且这种推理方式有点教条。但我没有问题称它们 为单元测试的代码气味。我觉得他们的使用应该是合理的,原因如下:

  1. 测试代码本质上是程序化的。通常,设置/拆卸确实会降低测试的可读性/焦点。
  2. 设置方法的初始化往往比任何单个测试所需的更多。当被滥用时,它们会变得笨拙。Object Mothers、Test Data Builders,也许像 FactoryGirl 这样的框架似乎更擅长初始化测试数据。
  3. 他们鼓励“上下文膨胀”——测试上下文越大,它的可维护性就越差。

如果我的设置/拆卸没有这样做,我认为它们的使用是有道理的。测试中总会有一些重复。Neal Ford 将其描述为“测试可以是湿的,但不能浸透……” 此外,我认为当我们不是专门讨论单元测试,而是更广泛地讨论集成测试时,它们的使用更合理。

独自工作,这从来都不是问题。但是我发现在团队环境中维护测试套件非常困难,这往往是因为我们不能立即理解彼此的代码,或者不想通过它来理解它。从测试的角度来看,我发现在测试中允许一些重复可以减轻这种负担。

不过,我很想听听其他人对此有何看法。

于 2009-07-06T14:43:39.493 回答
0

如果您需要设置和拆卸来使您的单元测试工作,也许您真正需要的是模拟对象?

于 2009-07-07T03:43:48.840 回答