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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/512184/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-11 15:35:28  来源:igfitidea点击:

Best Practice: Initialize JUnit class fields in setUp() or at declaration?

javajunit

提问by Craig P. Motlin

Should I initialize class fields at declaration like this?

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

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
    }
}

Or in setUp() like this?

或者像这样在 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
    }
}

I tend to use the first form because it's more concise, and allows me to use final fields. If I don't needto use the setUp() method for set-up, should I still use it, and why?

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

Clarification:JUnit will instantiate the test class once per test method. That means listwill be created once per test, regardless of where I declare it. It also means there are no temporal dependencies between the tests. So it seems like there are no advantages to using setUp(). However the JUnit FAQ has many examples that initialize an empty collection in setUp(), so I figure there must be a reason.

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

采纳答案by Moss Collum

If you're wondering specifically about the examples in the JUnit FAQ, such as the basic test template, I think the best practice being shown off there is that the class under testshould be instantiated in your setUp method (or in a test method).

如果您特别想知道 JUnit 常见问题解答中的示例,例如基本测试模板,我认为展示的最佳实践是应该在您的 setUp 方法(或测试方法)中实例化被测类.

When the JUnit examples create an ArrayList in the setUp method, they all go on to test the behavior of that ArrayList, with cases like testIndexOutOfBoundException, testEmptyCollection, and the like. The perspective there is of someone writing a class and making sure it works right.

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

You should probably do the same when testing your own classes: create your object in setUp or in a test method, so that you'll be able to get reasonable output if you break it later.

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

On the other hand, if you use a Java collection class (or other library class, for that matter) in your test code, it's probably not because you want to test it--it's just part of the test fixture. In this case, you can safely assume it works as intended, so initializing it in the declaration won't be a problem.

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

For what it's worth, I work on a reasonably large, several-year-old, TDD-developed code base. We habitually initialize things in their declarations in test code, and in the year and a half that I've been on this project, it has never caused a problem. So there's at least some anecdotal evidence that it's a reasonable thing to do.

就其价值而言,我在一个相当大的、已有数年历史的、TDD 开发的代码库上工作。我们习惯性地在测试代码的声明中初始化一些东西,在我参与这个项目的一年半时间里,它从未引起过问题。所以至少有一些轶事证据表明这是一件合理的事情。

回答by Alex B

I prefer readability first which most often does not use the setup method. I make an exception when a basic setup operation takes a long time and is repeated within each test.
At that point I move that functionality into a setup method using the @BeforeClassannotation (optimize later).

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

Example of optimization using the @BeforeClasssetup method: I use dbunit for some database functional tests. The setup method is responsible for putting the database in a known state (very slow... 30 seconds - 2 minutes depending on amount of data). I load this data in the setup method annotated with @BeforeClassand then run 10-20 tests against the same set of data as opposed to re-loading/initializing the database inside each test.

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

Using Junit 3.8 (extending TestCase as shown in your example) requires writing a little more code than just adding an annotation, but the "run once before class setup" is still possible.

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

回答by Jurgen Hannaert

In addition to Alex B's answer.

除了亚历克斯 B 的回答。

It is even required to use the setUp method to instantiate resources in a certain state. Doing this in the constructor is not only a matter of timings, but because of the way JUnit runs the tests, each test state would be erased after running one.

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

JUnit first creates instances of the testClass for each test method and starts running the tests after each instance is created. Before running the test method, its setup method is ran, in which some state can be prepared.

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

If the database state would be created in the constructor, all instances would instantiate the db state right after each other, before running each tests. As of the second test, tests would run with a dirty state.

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

JUnits lifecycle:

JUnit 生命周期:

  1. Create a different testclass instance for each test method
  2. Repeat for each testclass instance: call setup + call the testmethod
  1. 为每个测试方法创建不同的测试类实例
  2. 对每个测试类实例重复:调用 setup + 调用 testmethod

With some loggings in a test with two test methods you get: (number is the hashcode)

通过使用两种测试方法在测试中进行一些日志记录,您会得到:(数字是哈希码)

  • Creating new instance: 5718203
  • Creating new instance: 5947506
  • Setup: 5718203
  • TestOne: 5718203
  • Setup: 5947506
  • TestTwo: 5947506
  • 创建新实例:5718203
  • 创建新实例:5947506
  • 设置:5718203
  • 测试一:5718203
  • 设置:5947506
  • 测试二:5947506

回答by Craig P. Motlin

I started digging myself and I found one potential advantage of using setUp(). If any exceptions are thrown during the execution of setUp(), JUnit will print a very helpful stack trace. On the other hand, if an exception is thrown during object construction, the error message simply says JUnit was unable to instantiate the test case and you don't see the line number where the failure occurred, probably because JUnit uses reflection to instantiate the test classes.

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

None of this applies to the example of creating an empty collection, since that will never throw, but it is an advantage of the setUp()method.

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

回答by amit

In your case (creating a list) there is no difference in practice. But generally it is better to use setUp(), because that will help Junit to report Exceptions correctly. If an exception occurs in constructor/initializer of a Test, that is a test failure. However, if an exception occurs during setup, it is natural to think of it as some issue in setting up the test, and junit reports it appropriately.

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

回答by Eddie

Since each test is executed independently, with a fresh instance of the object, there's not much point to the Test object having any internal state except that shared between setUp()and an individual test and tearDown(). This is one reason (in addition to the reasons others gave) that it's good to use the setUp()method.

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

Note: It's a bad idea for a JUnit test object to maintain static state! If you make use of static variable in your tests for anything other than tracking or diagnostic purposes, you are invalidating part of the purpose of JUnit, which is that the tests can (an may) be run in any order, each test running with a fresh, clean state.

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

The advantages to using setUp()is that you don't have to cut-and-paste initialization code in every test method and that you don't have test setup code in the constructor. In your case, there is little difference. Just creating an empty list can be done safely as you show it or in the constructor as it's a trivial initialization. However, as you and others have pointed out, anything that can possibly throw an Exceptionshould be done in setUp()so you get the diagnostic stack dump if it fails.

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

In your case, where you are just creating an empty list, I would do the same way you are suggesting: Assign the new list at the point of declaration. Especially because this way you have the option of marking it finalif this makes sense for your test class.

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

回答by dsaff

In JUnit 3, your field initializers will be run once per test method before any tests are run. As long as your field values are small in memory, take little set up time, and do not affect global state, using field initializers is technically fine. However, if those do not hold, you may end up consuming a lot of memory or time setting up your fields before the first test is run, and possibly even running out of memory. For this reason, many developers always set field values in the setUp() method, where it's always safe, even when it's not strictly necessary.

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

Note that in JUnit 4, test object initialization happens right before test running, and so using field initializers is safer, and recommended style.

请注意,在 JUnit 4 中,测试对象初始化发生在测试运行之前,因此使用字段初始化程序更安全,并且是推荐的风格。

回答by Nils von Barth

In JUnit 4:

在 JUnit 4 中:

  • For the Class Under Test, initialize in a @Beforemethod, to catch failures.
  • For other classes, initialize in the declaration...
    • ...for brevity, and to mark fields final, exactly as stated in the question,
    • ...unless it is complex initializationthat could fail, in which case use @Before, to catch failures.
  • For global state(esp. slow initialization, like a database), use @BeforeClass, but be carefulof dependencies between tests.
  • Initialization of an object used in a single testshould of course be done in the test method itself.
  • 对于Class Under Test,在@Before方法中初始化,以捕获失败。
  • 对于其他类,在声明中初始化...
    • ...为简洁起见,并标记 fields final,完全如问题中所述,
    • ...除非是复杂的初始化可能会失败,在这种情况下使用@Before来捕获失败。
  • 对于全局状态(特别是慢初始化,如数据库),使用@BeforeClass,但要注意测试之间的依赖关系。
  • 单个测试中使用的对象的初始化当然应该在测试方法本身中完成。

Initializing in a @Beforemethod or test method allows you to get better error reporting on failures. This is especially useful for instantiating the Class Under Test (which you might break), but is also useful for calling external systems, like filesystem access ("file not found") or connecting to a database ("connection refused").

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

It is acceptableto have a simple standard and always use @Before(clear errors but verbose) or always initialize in declaration (concise but gives confusing errors), since complex coding rules are hard to follow, and this isn't a big deal.

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

Initializing in setUpis a relic of JUnit 3, where all test instances were initialized eagerly, which causes problems (speed, memory, resource exhaustion) if you do expensive initialization. Thus best practice was to do expensive initialization in setUp, which was only run when the test was executed. This no longer applies, so it is much less necessary to use setUp.

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

This summarizes several other replies that bury the lede, notably by Craig P. Motlin (question itself and self-answer), Moss Collum (class under test), and dsaff.

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

回答by davidxxx

  • The constant values (uses in fixtures or assertions) should be initialized in their declarations and final(as never change)

  • the object under test should be initialized in the setup method because we may set things on. Of course we may not set something now but we could set it later. Instantiating in the init method would ease the changes.

  • dependencies of the object under test if these are mocked, should not even be instantiated by yourself : today the mock frameworks can instantiate it by reflection.

  • 常量值(在固定装置或断言中使用)应该在它们的声明中初始化并且final(永远不会改变)

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

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

A test without dependency to mock could look like :

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

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

A test with dependencies to isolate could look like :

具有要隔离的依赖项的测试可能如下所示:

@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()
         ...
    }
}