Java setUp/tearDown (@Before/@After) 为什么我们在 JUnit 中需要它们?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3648712/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
setUp/tearDown (@Before/@After) why we need them in JUnit?
提问by mhshams
I believe that we are all know that setUp (@Before) will execute before any test method and tearDown(@After) will execute after test method.
相信大家都知道setUp(@Before)会在任何测试方法之前执行,而tearDown(@After)会在测试方法之后执行。
Also we know that Junit will create one instance of Test per test method.
我们也知道 Junit 将为每个测试方法创建一个 Test 实例。
my question is that can we just move setUp method content to class Constructor and remove setUp method? is there any specific reason to keep setUp method?
我的问题是我们可以将 setUp 方法内容移动到类 Constructor 并删除 setUp 方法吗?保留 setUp 方法有什么具体原因吗?
采纳答案by Pascal Thivent
This (old) JUnit best practicesarticle puts it like this:
这篇(旧的)JUnit 最佳实践文章是这样写的:
Do not use the test-case constructor to set up a test case
Setting up a test case in the constructor is not a good idea. Consider:
public class SomeTest extends TestCase public SomeTest (String testName) { super (testName); // Perform test set-up } }
Imagine that while performing the setup, the setup code throws an
IllegalStateException
. In response, JUnit would throw anAssertionFailedError
, indicating that the test case could not be instantiated. Here is an example of the resulting stack trace:junit.framework.AssertionFailedError: Cannot instantiate test case: test1 at junit.framework.Assert.fail(Assert.java:143) at junit.framework.TestSuite.runTest(TestSuite.java:178) at junit.framework.TestCase.runBare(TestCase.java:129) at junit.framework.TestResult.protect(TestResult.java:100) at junit.framework.TestResult.runProtected(TestResult.java:117) at junit.framework.TestResult.run(TestResult.java:103) at junit.framework.TestCase.run(TestCase.java:120) at junit.framework.TestSuite.run(TestSuite.java, Compiled Code) at junit.ui.TestRunner2.run(TestRunner.java:429)
This stack trace proves rather uninformative; it only indicates that the test case could not be instantiated. It doesn't detail the original error's location or place of origin. This lack of information makes it hard to deduce the exception's underlying cause.
Instead of setting up the data in the constructor, perform test setup by overriding
setUp()
. Any exception thrown withinsetUp()
is reported correctly. Compare this stack trace with the previous example:java.lang.IllegalStateException: Oops at bp.DTC.setUp(DTC.java:34) at junit.framework.TestCase.runBare(TestCase.java:127) at junit.framework.TestResult.protect(TestResult.java:100) at junit.framework.TestResult.runProtected(TestResult.java:117) at junit.framework.TestResult.run(TestResult.java:103) ...
This stack trace is much more informative; it shows which exception was thrown (
IllegalStateException
) and from where. That makes it far easier to explain the test setup's failure.
不要使用测试用例构造函数来设置测试用例
在构造函数中设置测试用例不是一个好主意。考虑:
public class SomeTest extends TestCase public SomeTest (String testName) { super (testName); // Perform test set-up } }
想象一下,在执行设置时,设置代码抛出一个
IllegalStateException
. 作为响应,JUnit 会抛出一个AssertionFailedError
,表明无法实例化测试用例。这是生成的堆栈跟踪的示例:junit.framework.AssertionFailedError: Cannot instantiate test case: test1 at junit.framework.Assert.fail(Assert.java:143) at junit.framework.TestSuite.runTest(TestSuite.java:178) at junit.framework.TestCase.runBare(TestCase.java:129) at junit.framework.TestResult.protect(TestResult.java:100) at junit.framework.TestResult.runProtected(TestResult.java:117) at junit.framework.TestResult.run(TestResult.java:103) at junit.framework.TestCase.run(TestCase.java:120) at junit.framework.TestSuite.run(TestSuite.java, Compiled Code) at junit.ui.TestRunner2.run(TestRunner.java:429)
这个堆栈跟踪证明相当缺乏信息;它仅表示无法实例化测试用例。它没有详细说明原始错误的位置或起源地。这种信息的缺乏使得很难推断出异常的根本原因。
不是在构造函数中设置数据,而是通过覆盖
setUp()
. 任何抛出的异常setUp()
都会正确报告。将此堆栈跟踪与上一个示例进行比较:java.lang.IllegalStateException: Oops at bp.DTC.setUp(DTC.java:34) at junit.framework.TestCase.runBare(TestCase.java:127) at junit.framework.TestResult.protect(TestResult.java:100) at junit.framework.TestResult.runProtected(TestResult.java:117) at junit.framework.TestResult.run(TestResult.java:103) ...
此堆栈跟踪信息更多;它显示抛出了哪个异常 (
IllegalStateException
) 以及从哪里抛出。这使得解释测试设置的失败变得容易得多。
回答by Steve B.
The reason you need this is that for many tests you often need to initialize state before each test so that the tests can all make assumptions about the start state they're running in.
您需要这样做的原因是,对于许多测试,您通常需要在每次测试之前初始化状态,以便所有测试都可以对它们正在运行的开始状态做出假设。
Suppose your test class wraps, say database access. After each test you'd want to remove whatever changes your tests have made to the db - if you didn't do that, each test runs against a slightly modified database. Additionally, any given test may see a different set of changes if some subset of the previous tests have failed. For example, suppose test1 does an insert, test2 checks that you're accurately reading the table size. Day 1, test1 fails, and 0 is correct. Day 2, test1 succeeds, and 1 is correct?
假设您的测试类包装,比如说数据库访问。每次测试之后,您都希望删除您的测试对数据库所做的任何更改 - 如果您不这样做,则每个测试都会针对稍微修改过的数据库运行。此外,如果先前测试的某些子集失败,则任何给定的测试都可能会看到一组不同的更改。例如,假设 test1 执行插入操作,test2 会检查您是否准确读取了表大小。第 1 天,test1 失败,0 是正确的。第2天,test1成功,1正确?
BTW, junit also supports @BeforeClass
if you want to do a global setup, and setup and teardowns are optional.
顺便说一句,@BeforeClass
如果你想做全局设置,junit 也支持,设置和拆卸是可选的。
回答by RichN
A custom runner such as SpringJUnit4ClassRunner
may need to run some codes between the constructor and @Before
method. In this case, the runner may inject some dependency which the @Before
methods needs. But dependency injection can only be run after the object is constructed.
自定义SpringJUnit4ClassRunner
运行程序,例如可能需要在构造函数和@Before
方法之间运行一些代码。在这种情况下,运行程序可能会注入一些@Before
方法需要的依赖项。但是依赖注入只能在对象构造完成后运行。
回答by Bert F
Here are 3 good reasons why. In summary:
这里有 3 个很好的理由。总之:
Some situations may prefer to defer setting up test fixtures as long as possible, to just beforethe test case executes.
Some test cases may be part of a deep test case inheritance hierarchy. It may be preferable to defer setting up test fixtures until the full hierarchy of constructors has completed.
You get better diagnostics if setup code fails in setUp() rather than if it fails in the constructor.
在某些情况下,可能更喜欢将测试装置的设置尽可能推迟到测试用例执行之前。
一些测试用例可能是深层测试用例继承层次结构的一部分。最好推迟设置测试装置,直到构造函数的完整层次结构完成。
如果设置代码在 setUp() 中失败,而不是在构造函数中失败,您将获得更好的诊断。
1. Defer setting up fixtures until just before test case
1. 将设置装置推迟到测试用例之前
Design for Usability
http://www.artima.com/weblogs/viewpost.jsp?thread=70189
可用性设计
http://www.artima.com/weblogs/viewpost.jsp?thread=70189
... And as Elliotte Rusty Harold put it, if you're going to create a new TestCase instance for each test method, "why the hell bother with a setUp() method?"You can just use the TestCase constructor.
I've heard Bruce Eckel point out that there is one subtle differencebetween creating your fixture in setUp() versus creating it in the TestCase constructor. JUnit creates all the TestCase instances up front, and then for each instance, calls setup(), the test method, and tearDown(). In other words, the subtle difference is that constructors are all invoked in batch up front, whereas the setUp() method is called right before each test method. But this seems to be not that useful a difference in practice.
...正如 Elliotte Rusty Harold 所说,如果您要为每个测试方法创建一个新的 TestCase 实例,“为什么要为 setUp() 方法烦恼呢?” 您可以只使用 TestCase 构造函数。
我听说 Bruce Eckel 指出在 setUp() 中创建您的装置与在 TestCase 构造函数中创建它之间存在一个微妙的区别。JUnit 预先创建所有 TestCase 实例,然后为每个实例调用 setup()、测试方法和 tearDown()。换句话说,细微的区别在于构造函数都是预先批量调用的,而 setUp() 方法是在每个测试方法之前调用的。但这在实践中似乎不是那么有用。
2. Defer setting up fixtures until after all test cases are instantiated
2. 推迟设置fixtures,直到所有的测试用例都被实例化
ETutorial's Java Extreme Programming - 4.6 Set Up and Tear Down
http://etutorials.org/Programming/Java+extreme+programming/Chapter+4.+JUnit/4.6+Set+Up+and+Tear+Down/
ETutorial 的 Java 极限编程 - 4.6 设置和拆卸
http://etutorials.org/Programming/Java+extreme+programming/Chapter+4.+JUnit/4.6+Set+Up+and+Tear+Down/
You may be wondering why you should write a setUp( ) method instead of simply initializing fields in a test case's constructor. After all, since a new instance of the test case is created for each of its test methods, the constructor is always called before setUp( ). In a vast majority of cases, you can use the constructor instead of setUp( ) without any side effects.
In cases where your test case is part of a deeper inheritance hierarchy, you may wish to postpone object initialization until instances of derived [test] classes are fully constructed. This is a good technical reason why you might want to use setUp( ) instead of a constructor for initialization. Using setUp( ) and tearDown( ) is also good for documentation purposes, simply because it may make the code easier to read.
您可能想知道为什么要编写 setUp() 方法而不是简单地初始化测试用例构造函数中的字段。毕竟,由于测试用例的新实例是为其每个测试方法创建的,所以构造函数总是在 setUp() 之前调用。在绝大多数情况下,您可以使用构造函数代替 setUp() 而没有任何副作用。
如果您的测试用例是更深层次继承层次结构的一部分,您可能希望推迟对象初始化,直到完全构造派生 [测试] 类的实例。这是您可能想要使用 setUp() 而不是构造函数进行初始化的一个很好的技术原因。使用 setUp() 和 tearDown()也有利于文档目的,仅仅是因为它可以使代码更易于阅读。
3. Better diagnostics in case of setup failure
3. 设置失败时更好的诊断
JUnit best practices (JavaWorld)
http://www.javaworld.com/jw-12-2000/jw-1221-junit.html
JUnit 最佳实践 (JavaWorld)
http://www.javaworld.com/jw-12-2000/jw-1221-junit.html
Setting up a test case in the constructor is not a good idea. ...
Imagine [in code where setup is done in the test case constructor] that while performing the setup, the setup code throws an IllegalStateException. In response, JUnit would throw an AssertionFailedError, indicating that the test case could not be instantiated. ...
This stack trace [of an exception thrown in setup code in the test case constructor] proves rather uninformative; it only indicates that the test case could not be instantiated.
Instead of setting up the data in the constructor, perform test setup by overriding setUp(). Any exception thrown within setUp() is reported correctly....
This stack trace [of an exception thrown in setUp() method instead of the test case constructor] is much more informative; it shows which exception was thrown (IllegalStateException) and from where. That makes it far easier to explain the test setup's failure.
在构造函数中设置测试用例不是一个好主意。...
想象一下[在测试用例构造函数中完成设置的代码中],在执行设置时,设置代码抛出一个 IllegalStateException。作为响应,JUnit 将抛出 AssertionFailedError,表明无法实例化测试用例。...
这个堆栈跟踪[在测试用例构造函数中的设置代码中抛出的异常]证明相当没有信息;它仅表示无法实例化测试用例。
不是在构造函数中设置数据,而是通过覆盖 setUp() 执行测试设置。在 setUp() 中抛出的任何异常都会被正确报告。...
这个堆栈跟踪[在 setUp() 方法而不是测试用例构造函数中抛出的异常] 提供了更多信息;它显示抛出了哪个异常 (IllegalStateException) 以及从哪里抛出。这使得解释测试设置的失败变得容易得多。
回答by BjornS
At work we've discovered something rather interesting which answers your question. When you run a test suite, especially a large set of tests (200+) JUnit starts to use a LOT of memory, this is because ALL the tests are instanced before any actual test method is run.
在工作中,我们发现了一些相当有趣的东西来回答您的问题。当您运行测试套件,尤其是大量测试 (200+) 时,JUnit 开始使用大量内存,这是因为在运行任何实际测试方法之前,所有测试都已实例化。
We ran into a "memory leak" because of this because we used Spring to wire in some JPA EntiryManager objects for our database tests, this became A LOT of objects and a lot of memory and about half way through the tests we were getting OutOfMemory exceptions.
我们因此遇到了“内存泄漏”,因为我们使用 Spring 为我们的数据库测试连接了一些 JPA EntiryManager 对象,这变成了很多对象和大量内存,大约在测试进行到一半时我们得到了 OutOfMemory 异常.
IMHO, best practise is to use setUp and tearDown to inject your dependencies and null out any and all class references, this will make your tests run faster and save you a lot of head ache!
恕我直言,最佳实践是使用 setUp 和 tearDown 注入您的依赖项并清除所有类引用,这将使您的测试运行得更快,并为您省去很多麻烦!
Hope you learn from our mistakes :)
希望你从我们的错误中吸取教训:)
回答by GoingWell
I think some reason should like the following:
我认为某些原因应该如下:
- If you move @Before contents to Constructor, That's fine, but the @After contents where you get to move?
- The differences of Constructor and @Before/@After is that Constructor should be used to instance some for class, @Before/@After is for preparing test case resources.
- 如果您将@Before 内容移动到构造函数,那很好,但是您要移动的@After 内容呢?
- Constructor 和@Before/@After 的区别在于Constructor 应该用于为类实例化一些,@Before/@After 用于准备测试用例资源。