设置/拆卸是否会损害测试的可维护性?
这似乎引发了对另一个问题的一些讨论和我 认为值得深入讨论自己的问题。
DRY 原则似乎是我们对抗维护的首选武器 问题,但是测试代码的维护又如何呢? 遵循相同的经验法则 申请?
开发者测试社区中的一些强烈声音认为 安装和拆卸是有害的,应该避免......仅举几例:
事实上,出于这个原因,xUnit.net 已将它们从框架中完全删除 (尽管有方法来绕过这种自我施加的限制) 。
你有什么经验吗? 安装/拆卸是否会损害或有助于测试可维护性?
更新:JUnit4 或 TestNG 中提供的更细粒度的构造(@BeforeClass、@BeforeGroups 等)是否会有所作为?
This seemed to spark a bit of conversation on another question and I
thought it worthy to spin into its own question.
The DRY principle seems to be our weapon-of-choice for fighting maintenance
problems, but what about the maintenance of test code? Do the same rules of thumb
apply?
A few strong voices in the developer testing community are of the opinion that
setup and teardown are harmful and should be avoided... to name a few:
In fact, xUnit.net has removed them from the framework altogether for this very reason
(though there are ways to get around this self-imposed limitation).
What has been your experience? Do setup/teardown hurt or help test maintainability?
UPDATE: do more fine-grained constructs like those available in JUnit4 or TestNG (@BeforeClass, @BeforeGroups, etc.) make a difference?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
大多数(如果不是全部)设置和拆卸方法的有效用途都可以编写为工厂方法,这允许 DRY,而不会陷入似乎受到设置/拆卸范例困扰的问题。
如果您正在实施拆卸,通常这意味着您不是在进行单元测试,而是在进行集成测试。 很多人以此作为不进行拆卸的理由,但在我看来,应该同时进行集成和单元测试。 我个人会将它们分成单独的程序集,但我认为一个好的测试框架应该能够支持这两种类型的测试。 并非所有好的测试都是单元测试。
然而,在设置中,似乎有很多原因导致您需要在实际运行测试之前执行一些操作。 例如,构建对象状态以准备测试(例如设置依赖注入框架)。 这是进行设置的正当理由,但也可以通过工厂轻松完成。
此外,类和方法级别的设置/拆卸之间也有区别。 在考虑您要做什么时需要牢记这一点。
我在使用安装/拆卸范例时遇到的最大问题是我的测试并不总是遵循相同的模式。 这让我转而使用工厂模式,这让我能够保持 DRY,同时又易于阅读,并且不会让其他开发人员感到困惑。 走工厂路线,我已经能够鱼与熊掌兼得了。
The majority (if not all) of valid uses for setup and teardown methods can be written as factory methods which allows for DRY without getting into issues that seem to be plagued with the setup/teardown paradigm.
If you're implementing the teardown, typically this means you're not doing a unit test, but rather an integration test. A lot of people use this as a reason to not have a teardown, but IMO there should be both integration and unit test. I would personally separate them into separate assemblies, but I think a good testing framework should be able to support both types of testing. Not all good testing is going to be unit testing.
However, with the setup there seems to be a number of reasons why you need to do things before a test is actually run. For example, construction of object state to prep for the test (for instance setting up a Dependency Injection framework). This is a valid reason for a setup, but could just as easily be done with a factory.
Also, there is a distinction between class and method level setup/teardown. That needs to be kept in mind when considering what you're trying to do.
My biggest problem that I have had with using the setup/teardown paradigm is that my tests don't always follow the same pattern. This has brought me into using factory patterns instead, which allows me to have DRY while at the same time being readable and not at all confusing to other developers. Going the factory route, I've been able to have my cake and eat it to.
他们确实对我们的测试可维护性有帮助。 我们的“单元”测试实际上是完整的端到端集成测试,写入数据库并检查结果。 这不是我的错,当我来到这里时他们就是这样,我正在努力改变一切。
无论如何,如果一个测试失败,它会继续进行下一个测试,尝试在数据库中输入第一个测试中的相同用户,这违反了唯一性约束,并且失败从那里开始级联。 将用户创建/删除移动到 [Fixture][SetUp|TearDown] 方法中,使我们能够看到失败的一个测试,而不会出现一切混乱的情况,并使我的生活变得更加轻松和稳定。
They've really helped with our test maintainability. Our "unit" tests are actually full end-to-end integration tests that write to the DB and check the results. Not my fault, they were like that when I got here, and I'm working to change things.
Anyway, if one test failed, it went on to the next one, trying to enter the same user from the first test in the DB, violating a uniqueness constraint, and the failures just cascaded from there. Moving the user creation/deletion into the [Fixture][SetUp|TearDown] methods allowed us to see the one test that failed without everything going haywire, and made my life a lot easier and less stabby.
我认为 DRY 原则同样适用于测试和代码,但其应用是不同的。 在代码中,您会花费更多的精力,避免在代码的两个不同部分中执行相同的操作。 在测试中,需要这样做(进行大量相同的设置)肯定是一种味道,但解决方案不一定是将重复项分解到设置方法中。 它可能会使状态更容易在类本身中设置,或者隔离被测试的代码,因此它不太依赖于有意义的状态量。
考虑到每次测试只测试一件事情的总体目标,在某些情况下(例如创建某种类型的对象)确实不可能避免一遍又一遍地做很多相同的事情。 如果您发现有很多这样的情况,那么可能值得重新考虑测试方法,例如引入参数化测试等。
我认为设置和拆卸应该主要用于建立环境(例如注入以使环境成为测试环境而不是生产环境),并且不应包含作为测试重要组成部分的步骤。
I think the DRY principle applies just as much for tests as it does for code, however its application is different. In code you go to much greater lengths to literally not do the same thing in two different parts of the code. In tests the need to do that (do a lot of the same setup) is certainly a smell, but the solution is not necessarily to factor out the duplication into a setup method. It may be make the state easier to set up in the class itself or to isolate the code under test so it is less dependent on this amount of state to be meaningful.
Given the general goal of only testing one thing per test, it really isn't possible to avoid doing a lot of the same thing over and over again in certain cases (such as creating an object of a certain type). If you find you have a lot of that, it may be worth rethinking the test approach, such as introducing parametrized tests and the like.
I think setup and teardown should be primarily for establishing the environment (such as injections to make the environment a test one rather than a production one), and should not contain steps that are part and parcel of the test.
我同意 Joseph 所说的一切,特别是关于tearDown 是编写集成测试的标志的部分(99% 的时间都是我使用它的目的),但除此之外我想说的是,使用设置的好坏可以很好地指示何时应将测试逻辑分组在一起以及何时应将它们拆分为多个测试类。
将测试应用于遗留代码时,我对大型设置方法没有任何问题,但设置应该对于套件中的每个测试都是通用的。 当您发现自己的设置方法确实执行了多项设置时,那么就应该将测试分为多个案例。
按照 “测试驱动” ,设置方法来自于删除测试用例中的重复项。
I agree with everything Joseph has to say, especially the part about tearDown being a sign of writing integration tests (and 99% of the time is what I've used it for), but in addition to that I'd say that the use of setup is a good indicator of when tests should be logically grouped together and when they should be split into multiple test classes.
I have no problem with large setup methods when applying tests to legacy code, but the setup should be common to every test in the suite. When you find yourself having the setup method really doing multiple bits of setup, then it's time to split your tests into multiple cases.
Following the examples in "Test Driven", the setup method comes about from removing duplication in the test cases.
我在 Java 和 Python 中经常使用 setup,经常用来设置协作者(无论是真实的还是测试的,具体取决于)。 如果被测试的对象没有构造函数或只有协作者作为构造函数,我将创建该对象。 对于简单的值类,我通常不会理会它们。
我在Java 中很少使用teardown。 在 Python 中,它被更频繁地使用,因为我更有可能更改全局状态(特别是,猴子修补模块以让这些模块的用户接受测试)。 在这种情况下,我想要一个在测试失败时保证被调用的拆解。
集成测试和功能测试(通常使用 xunit 框架)更有可能需要设置和拆卸。
要记住的一点是要考虑装置,而不仅仅是DRY。
I use setup quite frequently in Java and Python, frequently to set up collaborators (either real or test, depending). If the object under test has no constructors or just the collaborators as constructors I will create the object. For a simple value class I usually don't bother with them.
I use teardown very infrequently in Java. In Python it was used more often because I was more likely to change global state (in particular, monkey patching modules to get users of those modules under test). In that case I want a teardown that will guaranteed to be called if a test failed.
Integration tests and functional tests (which often use the xunit framework) are more likely to need setup and teardown.
The point to remember is to think about fixtures, not only DRY.
我对测试设置和拆卸方法本身没有问题。
对我来说问题是,如果您有测试设置和拆卸方法,则意味着每个测试都会重复使用相同的测试对象。 这是一个潜在的错误向量,就像您忘记在测试之间清理某些状态元素一样,您的测试结果可能会变得依赖于顺序。 我们真正想要的是不共享任何状态的测试。
xUnit.Net 摆脱了安装/拆卸,因为它为每个运行的测试创建一个新对象。 本质上,构造函数成为设置方法,而终结器成为拆卸方法。 测试之间不保留(对象级)状态,从而消除了这种潜在的错误向量。
我编写的大多数测试都有一定量的设置,即使它只是创建我需要的模拟并将正在测试的对象连接到模拟。 他们不做的是在测试之间共享任何状态。 拆卸只是确保我不会共享该状态。
I don't have an issue with test setup and teardown methods per se.
The issue to me is that if you have a test setup and teardown method, it implies that the same test object is being reused for each test. This is a potential error vector, as if you forget to clean up some element of state between tests, your test results can become order-dependent. What we really want is tests that do not share any state.
xUnit.Net gets rid of setup/teardown, because it creates a new object for each test that is run. In essence, the constructor becomes the setup method, and the finalizer becomes the teardown method. There's no (object-level) state held between tests, eliminating this potential error vector.
Most tests that I write have some amount of setup, even if it's just creating the mocks I need and wiring the object being tested up to the mocks. What they don't do is share any state between tests. Teardown is just making sure that I don't share that state.
我没有时间阅读您发布的两篇内容,但我特别喜欢这条评论:
设置和拆卸是方便的方法 - 除了使用默认构造函数等初始化类之外,它们不应该尝试做更多的事情。五个测试类中三个测试所需的通用代码不应该出现在那里 - 三个测试中的每一个应该直接调用这段代码。 这也可以防止测试互相干扰并破坏一堆测试,因为您更改了通用的初始化例程。 主要问题是这将在所有测试之前调用 - 而不仅仅是特定测试。 大多数测试应该很简单,更复杂的测试将需要初始化代码,但是当您不必跟踪设置中的复杂初始化和拆卸中的复杂销毁时,更容易看到简单测试的简单性。思考测试实际上应该完成什么。
I haven't had time to read both of what you posted, but I in particular liked this comment:
Setup and tear down are convenience methods - they shouldn't attempt to do much more than initialize a class using its default constructor, etc. Common code that three tests need in a five test class shouldn't appear there - each of the three tests should call this code directly. This also keeps tests from stepping on each other's toes and breaking a bunch of tests just because you changed a common initalization routine. The main problem is that this will be called before all tests - not just specific tests. Most tests should be simple, and the more complex ones will need initialization code, but it is easier to see the simplicity of the simple tests when you don't have to trace through a complex initialization in set up and complex destruction in tear down while thinking about what the test is actually supposed to accomplish.
就我个人而言,我发现设置和拆卸并不总是邪恶的,而且这种推理有点教条主义。 但我可以毫不犹豫地称他们为
用于单元测试的代码味道。 我认为它们的使用应该是合理的,原因如下:
如果我的设置/拆卸没有做到这一点,我认为它们的使用是有道理的。 测试中总会有一些重复。 Neal Ford 将此表述为“测试可以是湿的,但不能浸泡......”此外,我认为当我们不具体讨论单元测试而是更广泛地讨论集成测试时,它们的使用更合理。
对于我自己来说,这从来都不是一个真正的问题。 但我发现在团队环境中维护测试套件非常困难,这往往是因为我们不能立即理解彼此的代码,或者不想通过它来理解它。 从测试的角度来看,我发现允许一些重复测试可以减轻这种负担。
不过,我很想听听其他人对此有何看法。
Personally, I've found setup and teardown aren't always evil, and that this line of reasoning is a bit dogmatic. But I have no problem calling them a
code smell for unit tests. I feel their use should be justified, for a few reasons:
To the extent that my setup/teardown doesn't do this, I think their use is warranted. There will always be some duplication in tests. Neal Ford states this as "Tests can be wet but not soaking..." Also, I think their use is more justified when we're not talking about unit tests specifically, but integration tests more broadly.
Working on my own, this has never really been a problem. But I've found it very difficult to maintain test suites in a team setting, and it tends to be because we don't understand each other's code immediately, or don't want to have to step through it to understand it. From a test perspective, I've found allowing some duplication in tests eases this burden.
I'd love to hear how others feel about this, though.
如果您需要设置和拆卸才能使单元测试正常工作,也许您真正需要的是模拟对象?
If you need setup and teardown to make your unit tests work, maybe what you really need is mock objects?