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

探讨在JUnit测试中使用setUp方法与直接初始化的区别,强调不同场景下的适用性及异常处理的重要性。

java - 最佳实践:在setUp()或声明中初始化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()中初始化一个空集合,所以我认为必须有一个原因。

Craig P. Motlin asked 2019-07-18T23:31:41Z

9个解决方案

90 votes

如果你特别想知道JUnit FAQ中的例子,比如基本的测试模板,我认为最好的做法是在你的setUp方法(或测试方法)中实例化测试中的类。

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

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

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

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

Moss Collum answered 2019-07-18T23:32:23Z

42 votes

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

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

Craig P. Motlin answered 2019-07-18T23:32:55Z

18 votes

除了Alex B的回答。

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

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

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

JUnit生命周期:

为每个测试方法创建不同的testclass实例

对每个testclass实例重复:调用setup +调用testmethod

在测试中使用两种测试方法进行一些记录:(number是哈希码)

创建新实例:5718203

创建新实例:5947506

设置:5718203

TestOne:5718203

设置:5947506

TestTwo:5947506

Jurgen Hannaert answered 2019-07-18T23:34:47Z

9 votes

在JUnit 4中:

对于被测试类,请在setUp方法中初始化,以捕获故障。

对于其他类,请在声明中初始化.........为简洁起见,并标记字段setUp,完全按照问题所述,

...除非复杂的初始化可能会失败,在这种情况下使用setUp来捕获故障。

对于全局状态(特别是慢速初始化,如数据库),请使用setUp,但要注意测试之间的依赖关系。

在单个测试中使用的对象的初始化当然应该在测试方法本身中完成。

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

有一个简单的标准是可以接受的,并且总是使用setUp(清除错误但是详细)或总是在声明中初始化(简洁但是会产生令人困惑的错误),因为复杂的编码规则很难遵循,这不是什么大问题。

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

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

Nils von Barth answered 2019-07-18T23:36:22Z

7 votes

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

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

dsaff answered 2019-07-18T23:36:56Z

6 votes

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

amit answered 2019-07-18T23:37:21Z

5 votes

我更喜欢可读性,最常见的是不使用设置方法。 当基本设置操作需要很长时间并在每次测试中重复时,我会例外。

此时,我使用@BeforeClass注释(稍后优化)将该功能移动到设置方法中。

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

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

Alex B answered 2019-07-18T23:38:08Z

2 votes

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

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

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

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

Eddie answered 2019-07-18T23:38:59Z

0 votes

常量值(在夹具或断言中使用)应在其声明中初始化并且final(从不更改)

应该在setup方法中初始化被测对象,因为我们可以设置一些东西。 当然,我们现在可能没有设置,但我们可以稍后设置。 在init方法中实例化将简化更改。

如果这些被模拟,那么被测试对象的依赖关系甚至不应该被你自己实例化:今天模拟框架可以通过反射来实例化它。

不依赖于mock的测试可能如下所示:

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()

...

}

}

davidxxx answered 2019-07-18T23:39:53Z

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值