最佳实践: 在 setUp()或 at 声明中初始化 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
}
}

我倾向于使用第一种形式,因为它更简洁,并允许我使用 final 字段。如果我没有使用 需要的 setUp ()方法进行设置,那么我应该继续使用它吗? 为什么?

澄清: JUnit 将为每个测试方法实例化一次测试类。这意味着 list将在每个测试中创建一次,而不管我在哪里声明它。这也意味着测试之间没有时间依赖关系。因此,使用 setUp ()似乎没有什么好处。然而,JUnit FAQ 中有许多例子在 setUp ()中初始化一个空集合,所以我认为一定有原因。

169871 次浏览

我更喜欢可读性第一,往往不使用设置方法。当一个基本的安装操作需要很长的时间并且在每个测试中重复时,我会例外。
此时,我使用 @BeforeClass注释(稍后进行优化)将该功能移动到设置方法中。

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

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

除了 Alex B 的回答。

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

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

如果数据库状态将在构造函数中创建,那么所有实例将在运行每个测试之前紧接着彼此实例化 db 状态。在第二个测试中,测试将以脏状态运行。

JUnit 生命周期:

  1. 为每个测试方法创建一个不同的测试类实例
  2. 对每个 testclass 实例重复: 调用 setup + 调用 testmethod

通过使用两个测试方法在测试中进行一些日志记录,您可以得到: (number 是 hashcode)

  • 创建新实例: 5718203
  • 创建新实例: 5947506
  • 设置: 5718203
  • TestOne: 5718203
  • 设置: 5947506
  • 测试二: 5947506

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

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

在您的情况下(创建一个列表) ,在实践中没有区别。但是通常最好使用 setUp () ,因为这将帮助 Junit 正确地报告异常。如果在 Test 的构造函数/初始值设定项中发生异常,那就是测试 失败。但是,如果在安装过程中发生异常,自然会将其视为安装测试时的某个问题,并且 junit 会适当地报告它。

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

注意: 对于 JUnit 测试对象来说,维护静态状态是一个坏主意!如果您在测试中使用静态变量,而不是用于跟踪或诊断目的,那么您就失去了 JUnit 的部分用途,即测试可以(可以)以任何顺序运行,每个测试都以一种全新的、干净的状态运行。

使用 setUp()的好处是,您不必在每个测试方法中剪切和粘贴初始化代码,而且您在构造函数中没有测试设置代码。对你来说,没什么区别。只要创建一个空列表,就可以按照显示的方式安全地完成,或者在构造函数中完成,因为它只是一个简单的初始化。但是,正如您和其他人所指出的那样,任何可能抛出 Exception的操作都应该在 setUp()中完成,以便在出现故障时获得诊断堆栈转储。

在您的例子中,您只是创建了一个空列表,我将按照您建议的方法来做: 在声明点分配新列表。特别是因为这样你可以选择将它标记为 final,如果这对你的测试类有意义的话。

如果您特别想知道 JUnit FAQ 中的例子,比如 基本测试模板基本测试模板,我认为这里展示的最佳实践是 考试中的班级应该在 setUp 方法(或测试方法)中实例化。

当 JUnit 示例在 setUp 方法中创建 ArrayList 时,它们都会继续测试该 ArrayList 的行为,例如 testIndexOutOfbound Exception、 testEmptyCollection 等。从这个角度来看,有人编写了一个类,并确保它能正确工作。

在测试自己的类时,您可能也应该这样做: 在 setUp 或测试方法中创建对象,这样,如果稍后中断它,就能够获得合理的输出。

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

不管怎样,我所处理的是一个相当大的、有几年历史的、 TDD 开发的代码库。我们习惯在测试代码中的声明中初始化一些东西,在我参与这个项目的一年半时间里,它从来没有引起过问题。因此,至少有一些轶事证据认为,这样做是合理的。

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

请注意,在 JUnit 4中,测试对象初始化恰好发生在测试运行之前,因此使用字段初始化器更安全,推荐使用这种风格。

在 JUnit4中:

  • 对于 正在测试的班级,在 @Before方法中进行初始化,以捕获故障。
  • 对于 其他课程,在声明中初始化..。
    • ... 为了简短,并标记字段 final,正如问题中所述,
    • ... 除非是 复杂的初始化可能会失败,在这种情况下使用 @Before来捕捉失败。
  • 对于 全球化国家(特别是 初始化很慢,就像数据库一样) ,使用 @BeforeClass,但是使用 小心点来表示测试之间的依赖关系。
  • 单项测试中使用的对象的初始化当然应该在测试方法本身中完成。

@Before方法或测试方法中初始化允许您获得更好的故障错误报告。这对于实例化 Class Under Test (您可能会破坏它)特别有用,但是对于调用外部系统也很有用,比如文件系统访问(“ file not found”)或连接到数据库(“ connect  拒絕”)。

它是 可以接受,有一个简单的标准,总是使用 @Before(明确的错误,但详细)或总是在声明初始化(简洁,但给混淆的错误) ,因为复杂的编码规则很难遵循,这不是一个大问题。

setUp中进行初始化是 JUnit 3的遗留问题,在 JUnit 3中,所有的测试实例都是被急切地初始化的,如果进行昂贵的初始化,就会导致问题(速度、内存、资源耗尽)。因此,最佳实践是在 setUp中执行代价高昂的初始化,这只有在执行测试时才会运行。这不再适用,因此使用 setUp的必要性大大降低。

这篇文章总结了其他几个掩盖主题的回答,特别是 Craig P. Motlin (问题本身和自我回答)、 Moss Collum (测试中的类)和 dsaff。

  • 常量值(在 fixture 或断言中使用)应该在它们的声明和 final中进行初始化(永远不要更改)

  • 测试中的对象应该在 setup 方法中初始化,因为我们可以设置。当然,我们现在可能不会设置一些东西,但我们可以以后再设置它。在 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()
...
}
}