最佳实践:在setUp()或声明中初始化JUnit类字段?

时间:2009-02-04 16:44:07

标签: java junit

我应该按照这样的声明初始化类字段吗?

public class SomeTest extends TestCase
{
    private final List list = new ArrayList();

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

或者像这样的setUp()?

public class SomeTest extends TestCase
{
    private List list;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        this.list = new ArrayList();
    }

    public void testPopulateList()
    {
        // Add stuff to the list
        // Assert the list contains what I expect
    }
}

我倾向于使用第一种形式,因为它更简洁,并允许我使用最终字段。如果我不需要使用setUp()方法进行设置,我是否还应该使用它,为什么?

澄清: 每个测试方法,JUnit将实例化一次测试类。这意味着每次测试都会创建一次list,无论我在哪里声明它。这也意味着测试之间没有时间依赖关系。因此,使用setUp()似乎没有任何优势。但是JUnit FAQ有很多例子可以在setUp()中初始化一个空集合,所以我认为必须有一个原因。

9 个答案:

答案 0 :(得分:90)

如果您特别想知道JUnit常见问题解答中的示例,例如basic test template,我认为最好的做法是,测试中的应该是在setUp方法中(或在测试方法中)实例化。

当JUnit示例在setUp方法中创建一个ArrayList时,它们都会继续测试该ArrayList的行为,例如testIndexOutOfBoundException,testEmptyCollection等。从某人的角度来看,有人在写课并确保其正常运作。

在测试自己的类时,你应该做同样的事情:在setUp或测试方法中创建你的对象,这样你就可以在以后破解它时获得合理的输出。

另一方面,如果在测试代码中使用Java集合类(或其他类库),可能不是因为你想测试它 - 它只是测试夹具的一部分。在这种情况下,您可以安全地假设它按预期工作,因此在声明中初始化它不会是一个问题。

对于它的价值,我在一个相当大,几年,TDD开发的代码库上工作。我们习惯性地在测试代码的声明中初始化事物,并且在我参与这个项目的一年半中,它从未引起过任何问题。所以至少有一些轶事证据表明这是合理的事情。

答案 1 :(得分:42)

我开始挖掘自己,我找到了使用setUp()的一个潜在优势。如果在执行setUp()期间抛出任何异常,JUnit将打印一个非常有用的堆栈跟踪。另一方面,如果在对象构造期间抛出异常,则错误消息只是说JUnit无法实例化测试用例,并且您没有看到发生故障的行号,可能是因为JUnit使用反射来实例化测试类。

这些都不适用于创建空集合的示例,因为它永远不会抛出,但它是setUp()方法的优势。

答案 2 :(得分:18)

除了Alex B的回答。

甚至需要使用setUp方法来实例化某种状态的资源。在构造函数中执行此操作不仅仅是时间问题,而且由于JUnit运行测试的方式,每个测试状态将在运行之后被擦除。

JUnit首先为每个测试方法创建testClass的实例,并在创建每个实例后开始运行测试。在运行测试方法之前,运行其设置方法,其中可以准备一些状态。

如果将在构造函数中创建数据库状态,则在运行每个测试之前,所有实例都会在彼此之后立即实例化db状态。从第二次测试开始,测试将以脏状态运行。

JUnits生命周期:

  1. 为每个测试方法创建不同的testclass实例
  2. 对每个testclass实例重复:call setup + call the testmethod
  3. 在测试中使用两种测试方法进行一些记录:(number是哈希码)

    • 创建新实例:5718203
    • 创建新实例:5947506
    • 设置:5718203
    • TestOne:5718203
    • 设置:5947506
    • TestTwo:5947506

答案 3 :(得分:9)

在JUnit 4中:

  • 对于受测试类,请使用@Before方法进行初始化,以捕获失败。
  • 对于其他类,请在声明中初始化...
    • ...为简洁起见,并标记字段final,完全按照问题所述,
    • ...除非复杂初始化可能会失败,在这种情况下使用@Before来捕获失败。
  • 对于全局状态(特别是慢速初始化,就像数据库一样),使用@BeforeClass,但小心依赖测试之间。
  • 单一测试中使用的对象的初始化当然应该在测试方法本身中完成。

使用@Before方法或测试方法初始化可以让您更好地报告故障。这对于实例化被测试类(您可能会破坏)特别有用,但对于调用外部系统也很有用,例如文件系统访问(“找不到文件”)或连接到数据库(“拒绝连接”)。

可接受有一个简单的标准,并始终使用@Before(清除错误但是详细)或始终在声明中初始化(简洁但给出令人困惑的错误),因为复杂的编码规则是难以理解,这不是什么大问题。

setUp中初始化是JUnit 3的遗留物,其中所有测试实例都是急切初始化的,如果您进行昂贵的初始化,会导致问题(速度,内存,资源耗尽)。因此,最佳做法是在setUp中进行昂贵的初始化,这只在执行测试时运行。这不再适用,因此不太需要使用setUp

这总结了其他几个埋葬lede的回复,特别是Craig P. Motlin(问题本身和自我答案),Moss Collum(被测试的课程)和dsaff。

答案 4 :(得分:7)

在JUnit 3中,在运行任何测试之前,每个测试方法将运行一次字段初始值设定项。只要你的字段值在内存中很小,占用很少的设置时间,并且不影响全局状态,使用字段初始化器在技术上是很好的。但是,如果这些不成立,您可能最终会在第一次测试运行之前消耗大量内存或设置字段,甚至可能耗尽内存。出于这个原因,许多开发人员总是在setUp()方法中设置字段值,它始终是安全的,即使它不是绝对必要的。

请注意,在JUnit 4中,测试对象初始化恰好在测试运行之前发生,因此使用字段初始化器更安全,建议使用样式。

答案 5 :(得分:6)

在您的情况下(创建列表),实践中没有区别。但通常最好使用setUp(),因为这将有助于Junit正确报告异常。如果测试的构造函数/初始化程序中发生异常,那么测试失败。但是,如果在安装过程中发生异常,很自然会将其视为设置测试时的一些问题,并且junit会对其进行适当的报告。

答案 6 :(得分:5)

我首先选择可读性,但最常使用的是设置方法。当基本设置操作需要很长时间并且在每次测试中重复时,我会例外 此时,我使用@BeforeClass注释(稍后优化)将该功能移动到设置方法中。

使用@BeforeClass安装方法进行优化的示例:我将dbunit用于某些数据库功能测试。设置方法负责将数据库置于已知状态(非常慢...... 30秒 - 2分钟,具体取决于数据量)。我在使用@BeforeClass注释的setup方法中加载此数据,然后针对同一组数据运行10-20次测试,而不是在每次测试中重新加载/初始化数据库。

使用Junit 3.8(扩展TestCase,如示例所示)需要编写更多代码,而不仅仅是添加注释,但仍然可以在“类设置之前运行一次”。

答案 7 :(得分:2)

由于每个测试都是独立执行的,并且对象的新实例,除了在setUp()和单个测试tearDown()之间共享之外,没有太多指向Test对象具有任何内部状态。这是一个原因(除了其他人给出的原因),使用setUp()方法是好的。

注意:JUnit测试对象维护静态状态是个坏主意!如果您在测试中使用静态变量以用于跟踪或诊断目的以外的任何其他内容,那么您将使JUnit的部分目的无效,即测试可以(一个可以)以任何顺序运行,每个测试都运行一个清新干净的状态。

使用setUp()的优点是您不必在每个测试方法中剪切和粘贴初始化代码,并且您在构造函数中没有测试设置代码。在你的情况下,没有什么区别。只需创建一个空列表就可以在显示时安全地完成,也可以在构造函数中完成,因为它是一个简单的初始化。但是,正如您和其他人所指出的那样,任何可能抛出Exception的内容都应该在setUp()中完成,以便在失败时获得诊断堆栈转储。

在您的情况下,您只是在创建一个空列表,我会按照您建议的相同方式执行:在声明点分配新列表。特别是因为这样你可以选择标记它final,如果这对你的测试类有意义的话。

答案 8 :(得分:0)

  • 常数值(用于固定装置或断言中)应在其声明中初始化,final(永不改变)

  • 被测试的对象应该在设置方法中初始化,因为我们可能会将其设置为开。当然,我们可能现在不设置某些内容,但可以稍后进行设置。实例化init方法可以简化更改。

  • 被测试对象的依赖关系如果被嘲笑,即使您自己也不应该实例化:今天,模拟框架可以通过反射实例化它。

不依赖模拟的测试看起来像:

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Before
    public void beforeEach() {
       some = new Some(new Foo(), new Bar());
    } 

    @Test
    public void populateList()
         ...
    }
}

具有要隔离的依赖关系的测试看起来像:

@RunWith(org.mockito.runners.MockitoJUnitRunner.class)
public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String PREFIX_URL_WS = "http://foo.com/ws";

    @Mock
    Foo fooMock;

    @Mock
    Bar barMock;

    @Before
    public void beforeEach() {
       some = new Some(fooMock, barMock);
    }

    @Test
    public void populateList()
         ...
    }
}