66

相信大家都知道 setUp(@Before) 会在任何测试方法之前执行,而 tearDown(@After) 会在测试方法之后执行。

我们也知道 Junit 将为每个测试方法创建一个 Test 实例。

我的问题是我们可以将 setUp 方法内容移动到类 Constructor 并删除 setUp 方法吗?是否有任何特定理由保留 setUp 方法?

4

6 回答 6

59

这篇(旧的)JUnit 最佳实践文章是这样描述的:

不要使用测试用例构造函数来设置测试用例

在构造函数中设置测试用例不是一个好主意。考虑:

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)以及从哪里抛出。这使得解释测试设置的失败变得容易得多。

于 2010-09-06T02:50:38.867 回答
26

在工作中,我们发现了一些相当有趣的东西,可以回答您的问题。当您运行一个测试套件,尤其是一大组测试(200+)时,JUnit 开始使用大量内存,这是因为所有测试都是在运行任何实际测试方法之前实例化的。

我们遇到了“内存泄漏”,因为我们使用 Spring 为我们的数据库测试连接了一些 JPA EntiryManager 对象,这变成了很多对象和大量内存,大约在测试进行到一半时,我们得到了 OutOfMemory 异常.

恕我直言,最佳实践是使用 setUp 和 tearDown 来注入您的依赖项并清除任何和所有类引用,这将使您的测试运行得更快并为您节省很多头痛!

希望你能从我们的错误中吸取教训:)

于 2010-09-06T22:54:00.977 回答
24

这里有 3 个很好的理由。总之:

  1. 在某些情况下,可能更愿意将设置测试装置尽可能推迟到测试用例执行之前。

  2. 一些测试用例可能是深层测试用例继承层次结构的一部分。最好推迟设置测试装置,直到构造器的完整层次结构完成。

  3. 如果设置代码在 setUp() 中失败而不是在构造函数中失败,您将获得更好的诊断。

1. 将夹具设置推迟到测试用例之前

可用性设计
http://www.artima.com/weblogs/viewpost.jsp?thread=70189

...正如 Elliotte Rusty Harold 所说,如果您要为每个测试方法创建一个新的 TestCase 实例,“为什么还要麻烦 setUp() 方法?” 您可以只使用 TestCase 构造函数。

我听说 Bruce Eckel 指出,在 setUp() 中创建夹具与在 TestCase 构造函数中创建夹具之间存在细微差别。JUnit 预先创建所有 TestCase 实例然后为每个实例调用 setup()、测试方法和 tearDown()。换句话说,细微的区别是构造函数都是在前面批量调用的,而 setUp() 方法是在每个测试方法之前调用的。但这在实践中似乎并没有那么有用。

2. 推迟设置夹具,直到所有测试用例都被实例化

ETutorial 的 Java Extreme Programming - 4.6 设置和拆卸
http://etutorials.org/Programming/Java+extreme+programming/Chapter+4.+JUnit/4.6+Set+Up+and+Tear+Down/

您可能想知道为什么要编写 setUp() 方法而不是简单地在测试用例的构造函数中初始化字段。毕竟,由于为每个测试方法创建了一个新的测试用例实例,构造函数总是在setUp()之前调用。在绝大多数情况下,您可以使用构造函数代替 setUp(),而不会产生任何副作用。

如果您的测试用例是更深层次的继承层次结构的一部分,您可能希望推迟对象初始化,直到派生 [test] 类的实例完全构造。这是一个很好的技术原因,为什么您可能希望使用 setUp() 而不是构造函数进行初始化。使用 setUp( ) 和 tearDown( )也有利于文档目的,仅仅是因为它可以使代码更易于阅读

3. 在设置失败的情况下更好的诊断

JUnit 最佳实践 (JavaWorld)
http://www.javaworld.com/jw-12-2000/jw-1221-junit.html

在构造函数中设置测试用例不是一个好主意。...

想象一下[在测试用例构造函数中完成设置的代码中],在执行设置时,设置代码会抛出 IllegalStateException。作为响应,JUnit 会抛出一个 AssertionFailedError,表明无法实例化测试用例。...

这个堆栈跟踪[在测试用例构造函数中的设置代码中抛出的异常]证明相当无信息;它仅表示无法实例化测试用例。

无需在构造函数中设置数据,而是通过覆盖 setUp() 来执行测试设置。在 setUp() 中抛出的任何异常都会被正确报告。...

这个堆栈跟踪[在 setUp() 方法而不是测试用例构造函数中抛出的异常] 提供了更多信息;它显示引发了哪个异常 (IllegalStateException) 以及从何处引发。这使得解释测试设置的失败变得容易得多。

于 2010-09-06T04:01:28.393 回答
6

自定义运行器,例如SpringJUnit4ClassRunner可能需要在构造函数和@Before方法之间运行一些代码。在这种情况下,运行程序可能会注入一些@Before方法需要的依赖项。但是依赖注入只能在对象构造完成后运行。

于 2010-09-06T03:02:20.453 回答
3

您需要这样做的原因是,对于许多测试,您通常需要在每次测试之前初始化状态,以便测试都可以对它们正在运行的开始状态做出假设。

假设您的测试类包装,说数据库访问。每次测试后,您都希望删除测试对数据库所做的任何更改 - 如果您不这样做,每个测试都会针对稍微修改的数据库运行。此外,如果先前测试的某些子集失败,任何给定的测试都可能会看到一组不同的更改。例如,假设 test1 进行了插入,test2 会检查您是否准确地读取了表大小。第 1 天,test1 失败,0 是正确的。第2天,test1成功,1正确?

@BeforeClass顺便说一句,如果您想进行全局设置,junit 也支持,并且设置和拆卸是可选的。

于 2010-09-06T02:36:50.610 回答
-4

我认为某些原因应该如下:

  1. 如果您将@Before 内容移动到构造函数,那很好,但是您要移动的@After 内容呢?
  2. Constructor 和@Before/@After 的不同之处在于Constructor 应该用于实例化一些类,@Before/@After 用于准备测试用例资源。
于 2010-09-18T13:06:13.543 回答