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

gcuhipw9  于 2023-06-06  发布在  其他
关注(0)|答案(9)|浏览(464)

我应该像这样在声明时初始化类字段吗?

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()中初始化空集合的例子,所以我认为这一定是有原因的。

nwlqm0z1

nwlqm0z11#

如果您特别想了解JUnit FAQ中的示例,例如基本测试模板,我认为那里展示的最佳实践是 class under test 应该在setUp方法(或测试方法)中示例化。
当JUnit示例在setUp方法中创建ArrayList时,它们都将继续测试该ArrayList的行为,例如testIndexOutOfBoundException、testEmptyCollection等。这里的观点是有人写了一个类,并确保它正确工作。
在测试自己的类时,你可能也应该这样做:在setUp或测试方法中创建你的对象,这样你就可以在以后破坏它的时候得到合理的输出。
另一方面,如果在测试代码中使用Java集合类(或者其他库类),可能不是因为要测试它--它只是测试夹具的一部分。在这种情况下,您可以放心地假设它按预期工作,因此在声明中初始化它不会有问题。
不管怎样,我使用的是一个相当大的、几年前的、TDD开发的代码库。我们习惯性地在测试代码的声明中初始化东西,在我参与这个项目的一年半时间里,它从来没有引起过问题。因此,至少有一些轶事证据表明,这是一个合理的事情。

px9o7tmv

px9o7tmv2#

我开始挖掘自己,发现使用setUp()的一个潜在优势。如果在执行setUp()期间抛出任何异常,JUnit将打印一个非常有用的堆栈跟踪。另一方面,如果在对象构造期间抛出异常,则错误消息只是说JUnit无法示例化测试用例,并且您看不到发生故障的行号,这可能是因为JUnit使用反射来示例化测试类。
这些都不适用于创建空集合的例子,因为它永远不会抛出,但这是setUp()方法的一个优点。

lvjbypge

lvjbypge3#

除了Alex B的回答。
甚至需要使用setUp方法来示例化处于某种状态的资源。在构造函数中这样做不仅是时间问题,而且由于JUnit运行测试的方式,每个测试状态在运行一个之后都会被删除。
JUnit首先为每个测试方法创建testClass的示例,并在创建每个示例后开始运行测试。在运行测试方法之前,运行其设置方法,其中可以准备一些状态。
如果数据库状态是在构造函数中创建的,那么在运行每个测试之前,所有示例都将依次示例化数据库状态。从第二个测试开始,测试将以脏状态运行。
JUnits生命周期:
1.为每个测试方法创建一个不同的TestClass示例
1.对每个测试类示例重复以下操作:调用setup +调用testmethod
使用两种测试方法在测试中使用一些日志记录,您可以获得:(数字是hashcode)

  • 正在创建新示例:5718203
  • 正在创建新示例:5947506
  • 设置:5718203
  • 测试一:5718203
  • 设置:5947506
  • 测试二:5947506
nvbavucw

nvbavucw4#

在JUnit 4中:

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

@Before方法或测试方法中进行初始化可以让您在失败时获得更好的错误报告。这对于示例化被测类(您可能会破坏它)特别有用,但对于调用外部系统也很有用,例如文件系统访问(“未找到文件”)或连接到数据库(“连接拒绝”)。
有一个简单的标准,总是使用@Before(明确的错误,但冗长)或总是在声明中初始化(简洁,但给出令人困惑的错误),这是可以接受的,因为复杂的编码规则很难遵循,这不是什么大问题。
setUp中进行初始化是JUnit 3的遗留问题,在JUnit 3中,所有测试示例都被急切地初始化,如果进行昂贵的初始化,这会导致问题(速度、内存、资源耗尽)。因此,最佳实践是在setUp中执行昂贵的初始化,该初始化仅在执行测试时运行。这不再适用,因此使用setUp的必要性要小得多。
这篇文章总结了其他几个掩盖了这一主题的回复,特别是克雷格·P·Motlin(问题本身和自我回答)、Moss Collum(测试类)和dsaff。

ujv3wf0j

ujv3wf0j5#

在JUnit 3中,每个测试方法 * 在运行任何测试 * 之前将运行一次字段初始化器。只要您的字段值在内存中很小,花费很少的设置时间,并且不影响全局状态,使用字段初始化器在技术上是可以的。但是,如果这些都不成立,那么在运行第一个测试之前,您可能会消耗大量的内存或时间来设置字段,甚至可能耗尽内存。出于这个原因,许多开发人员总是在setUp()方法中设置字段值,在这里它总是安全的,即使它不是严格必要的。
请注意,在JUnit 4中,测试对象初始化发生在测试运行之前,因此使用字段初始化器更安全,也是推荐的风格。

wr98u20j

wr98u20j6#

在你的情况下(创建一个列表)在实践中没有区别。但是通常使用setUp()更好,因为这将帮助Junit正确地报告异常。如果在Test的构造函数/初始化器中发生异常,则为test failure。但是,如果在设置过程中发生异常,很自然地会将其视为设置测试时的一些问题,junit会适当地报告它。

rseugnpd

rseugnpd7#

我更喜欢可读性第一,这通常不使用设置方法。当一个基本的设置操作需要很长时间并且在每个测试中重复时,我会破例。
此时,我使用@BeforeClass注解将该功能移到一个设置方法中(稍后进行优化)。
使用@BeforeClass设置方法的优化示例:我使用dbunit进行一些数据库功能测试。setup方法负责将数据库置于已知状态(非常慢……30秒- 2分钟,具体取决于数据量)。我在使用@BeforeClass注解的setup方法中加载这些数据,然后对同一组数据运行10-20个测试,而不是在每个测试中重新加载/初始化数据库。
使用Junit 3.8(如示例中所示扩展TestCase)需要编写比添加注解多一点的代码,但是“在类设置之前运行一次”仍然是可能的。

njthzxwz

njthzxwz8#

由于每个测试都是独立执行的,因此对于对象的新示例,除了setUp()和单个测试以及tearDown()之间共享的状态之外,Test对象没有太多的内部状态。这是使用setUp()方法的一个原因(除了其他人给出的原因之外)。
注意:让JUnit测试对象保持静态是个坏主意!如果您在测试中使用静态变量,而不是用于跟踪或诊断目的,那么您将使JUnit的部分目的无效,即测试可以(可能)以任何顺序运行,每个测试都以新鲜,干净的状态运行。
使用setUp()的优点是,您不必在每个测试方法中剪切并粘贴初始化代码,并且在构造函数中没有测试设置代码。在你的情况下,几乎没有区别。只要创建一个空列表就可以安全地在你展示它时完成,或者在构造函数中完成,因为它是一个简单的初始化。但是,正如您和其他人所指出的,任何可能抛出Exception的操作都应该在setUp()中完成,这样如果失败,您就可以获得诊断堆栈转储。
在你的例子中,你只是创建一个空列表,我会按照你建议的方式做:在声明时分配新列表。特别是因为这样您可以选择将其标记为final,如果这对您的测试类有意义的话。

h9a6wy2h

h9a6wy2h9#

  • 常量值(用于fixture或Assert)应该在它们的声明中初始化,并且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()
         ...
    }
}

相关问题