setUp/tearDown (@Before/@After) why we need them i

2019-01-10 09:18发布

问题:

I believe that we are all know that setUp (@Before) will execute before any test method and tearDown(@After) will execute after test method.

Also we know that Junit will create one instance of Test per test method.

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?

回答1:

This (old) JUnit best practices article puts it like this:

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 an AssertionFailedError, 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 within setUp() 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.



回答2:

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.

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.

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!

Hope you learn from our mistakes :)



回答3:

Here are 3 good reasons why. In summary:

  1. Some situations may prefer to defer setting up test fixtures as long as possible, to just before the test case executes.

  2. 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.

  3. You get better diagnostics if setup code fails in setUp() rather than if it fails in the constructor.

1. Defer setting up fixtures until just before test case

Design for Usability
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 difference between 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.

2. Defer setting up fixtures until after all test cases are instantiated

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/

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.

3. Better diagnostics in case of setup failure

JUnit best practices (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.



回答4:

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.



回答5:

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?

BTW, junit also supports @BeforeClass if you want to do a global setup, and setup and teardowns are optional.



回答6:

I think some reason should like the following:

  1. If you move @Before contents to Constructor, That's fine, but the @After contents where you get to move?
  2. 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.