【问题标题】:Writing JUnit tests with @BeforeClass @Before in Suites在套件中使用 @BeforeClass @Before 编写 JUnit 测试
【发布时间】:2023-03-08 08:50:02
【问题描述】:

我正在使用 JUnit 4 来测试带有内存数据库的后端系统。我正在使用 @BeforeClass @Before @After@AfterClass

到目前为止,它在班级级别上运行良好。

@BeforeClass 包含缓慢的数据库设置,但每个测试会话只需执行一次。

@Before 只是为下一次测试擦干净。速度挺快的。

我的测试看起来像这样:

class CompanyTest  {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run(); //Setup In Memory Database!!! Very time intensive!!!
    }
    @Before
    public void beforeTest() throws Exception {
        BeforeTest.run(); //Setup data in database. Relatively quick
    }


    @Test
    public void testCreateCompany() throws Exception {
        ///...
    }
    @Test
    public void testDeleteCompany() throws Exception {
        ///...
    }
    @Test
    public void testAdminCompany() throws Exception {
        ///...
    }


    @After
    public void afterTest() {
        AfterTest.run(); //clear data
    }
    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run(); //tear down database
    }
}

到目前为止,它在班级级别上运行良好。

我可以右键单击(在 Eclipse 中)单个测试,它将运行 @BeforeClass,然后运行 ​​@Before

我也可以(在 Eclipse 中)单击类本身,它只会运行一次 @BeforeClass,然后在每次测试之前运行 @Before

....但是这个原则如何扩展到套件级别?

我想在我的套件中的所有课程之前运行@BeforeClass。如果我这样写我的套件:

@Suite.SuiteClasses({ CompanyTest.class, CustomerTest.class, SomeOtherTest.class, })

public class AllTests {

    @BeforeClass
    public static void beforeAll() throws Exception {
        BeforeAll.run();
    }

    @AfterClass
    public static void afterAll() throws Exception {
        AfterAll.run();
    }
}

...我需要从我的所有测试类中删除 @BeforeClass。这很烦人,因为我有很多测试类,我不想删除我的 @BeforeClass 因为我想单独测试它们。

我的基本意思是:

是否有一种简单的方法(在 IDE 中单击鼠标)在 (a) 方法级别、(b) 类级别和 (c) 套件级别测试 JUnit 测试,同时保持会话级别设置和拆卸过程?

【问题讨论】:

  • 您可以添加一个静态计数器“nestingLevel”,而不是从所有测试类中删除 @BeforeClass,每个 BeforeAll.run() 递增,每个 AfterAll.run() 递减,然后执行仅当嵌套级别为 1 时才进行实际设置/拆卸。
  • 我在similar question 中给出的建议是使用 JUnit(类)规则并在规则中检查它是否之前已经初始化,在这种情况下跳过初始化。这应该允许您为套件和具体测试类重用规则。

标签: java junit junit4


【解决方案1】:

您需要的是通过不同的入口点管理全局状态。在您的 BeforeAll 或 AfterAll 类中,您可以保留一个静态引用,即 AtomicBoolean 来跟踪您是否已经执行它。

现在的问题 - beforeAll 和 afterAll 有两个单独的类 - 哪个类应该负责那个标志。

因此,我建议您定义一个 Rule,其中包含所有设置和拆卸逻辑(基本上是 BeforeAll 和 AfterAll 的所有代码)并管理用于跟踪初始化的全局状态。

基本上是这样的

class MyRule implements TestRule {
    static final AtomicBoolean INITIALIZED = new AtomicBoolean();

    @Override
    public Statement apply(final Statement base, final Description description) {

        return new Statement() {

            @Override
            public void evaluate() throws Throwable {
                boolean needsTearDown = false;
                try {
                    if (INITIALIZED.compareAndSet(false, true)) {
                        BeforeAll.run();
                        needsTearDown = true;
                    }
                    base.evaluate();
                } finally {
                    if (needsTearDown) {
                        AfterAll.run();
                        INITIALIZED.set(false);
                    }
                }
            }
        };
    }
}

在您的测试和套件中添加

class YourTestOrSuite {

  @ClassRule
  public static MyRule rule = new MyRule();
}

【讨论】:

  • 这不会编译。如果我将其粘贴到我的 IDE 中,那么括号似乎不正常。
  • (a) 方法级别:BeforeAll 未运行,(b) 类级别:BeforeAll 未运行,(c) 套件级别:AfterAll 在第一个类之后运行,因此所有其他类都失败第一节课结束后。
  • 我的错,'!' if 子句中的错误(如果预期值等于实际值,compareAndSet 返回 true)。当您直接在 SoW 中编码而不对其进行测试时会发生这种情况 :) ...它现在已修复并且应该可以工作
【解决方案2】:

所以我有一个类似的问题(数据库初始化、连接池、缓存数据等),需要在运行任何和所有测试之前完成一次。这通常在应用程序服务器启动时完成。对于需要这个的测试,我添加了一个基类:

public class BaseInitializedTest {
    private boolean isInitialized;

    @BeforeClass
    public static void init() {
        doOnetimeInit();

        // Do other per unique class initialization here
    }

    private static synchronized void doOnetimeInit() {
        if (!isInitialized) {
            // Do you one time init here

            isInitialized = true;
        }
    }

    @AfterClass
    public static void deinit() {
        // Cleanup
    }
}

然后是我需要初始化的测试:

public class SomethingTest extends BaseInitializedTest {
    @Test
    public testSomething() {
        // Your test here
    }
}

并非所有测试都需要初始化堆栈,但那些需要初始化的测试将从这个基类继承并正确处理 init。希望对您有所帮助。

【讨论】:

  • 这不起作用,因为如果我有三个不同的类从 BaseInitialisezedTest 继承,并且这三个类在我的套件中配置,那么如果我运行我的套件,我仍然会得到 @BeforeClass 运行三次(而不仅仅是一次)
  • 当然,这就是重点,init 会一直被调用,你需要确保真正的初始化只完成一次。我的 init 是通过一个单例完成的,所以一旦代码第一次初始化,重复调用 init 将什么也不做。对 init 使用静态同步方法,该方法在初始化时设置一个标志,如果设置则返回。我将在上面添加代码。
猜你喜欢
  • 2012-02-17
  • 1970-01-01
  • 2021-05-07
  • 2010-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-31
  • 1970-01-01
相关资源
最近更新 更多