在JUnit 5中,如何在所有测试之前运行代码

t9aqgxwy  于 2022-11-11  发布在  其他
关注(0)|答案(7)|浏览(287)

@BeforeAll注解标记了一个在 class 中的所有测试之前运行的方法。
http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
但是,有没有办法在所有类中的 * 所有 * 测试之前运行一些代码呢?
我想确保测试使用一组特定的数据库连接,并且这些连接的全局一次性设置必须在运行任何测试之前发生。

s71maibg

s71maibg1#

在JUnit 5中,通过创建一个自定义的扩展,现在可以实现这一点,您可以从该扩展中在根测试上下文上注册一个shutdown挂接。
您的扩展将如下所示;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

public class YourExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

    private static boolean started = false;

    @Override
    public void beforeAll(ExtensionContext context) {
        if (!started) {
            started = true;
            // Your "before all tests" startup logic goes here
            // The following line registers a callback hook when the root test context is shut down
            context.getRoot().getStore(GLOBAL).put("any unique name", this);
        }
    }

    @Override
    public void close() {
        // Your "after all tests" logic goes here
    }
}

然后,您需要至少执行一次的任何测试类都可以使用以下内容进行注解:

@ExtendWith({YourExtension.class})

在多个类上使用此扩展时,启动和关闭逻辑将只被调用一次。

svgewumm

svgewumm2#

@Philipp Gayret已经提供的答案在并行测试JUnit(即junit.jupiter.execution.parallel.enabled = true)时存在一些问题。
因此,我将解决方案修改为:

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public class BeforeAllTestsExtension extends BasicTestClass
        implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

    /**Gate keeper to prevent multiple Threads within the same routine */
    private static final Lock LOCK = new ReentrantLock();
    /**volatile boolean to tell other threads, when unblocked, whether they should try attempt start-up.  Alternatively, could use AtomicBoolean. */
    private static volatile boolean started = false;

    @Override
    public void beforeAll(final ExtensionContext context) throws Exception {
        // lock the access so only one Thread has access to it
        LOCK.lock();
        try {
            if (!started) {
                started = true;
                // Your "before all tests" startup logic goes here
                // The following line registers a callback hook when the root test context is
                // shut down
                context.getRoot().getStore(GLOBAL).put("any unique name", this);

                // do your work - which might take some time - 
                // or just uses more time than the simple check of a boolean
            }
        } finally {
            // free the access
            LOCK.unlock();
        }
    }

    @Override
    public void close() {
        // Your "after all tests" logic goes here
    }
}

如下所述,JUnit 5提供了一个自动扩展注册。为此,在src/test/resources/中添加一个名为/META-INF/services的目录,并添加一个名为org.junit.jupiter.api.extension.Extension的文件。将类的完全分类名称添加到该文件中,例如:

at.myPackage.BeforeAllTestsExtension

下一个在同一Junit配置文件中启用

junit.jupiter.extensions.autodetection.enabled=true

这样,扩展会自动附加到您的所有测试。

a1o7rhls

a1o7rhls3#

下面是一个更完整的代码片段,它是对@Philipp的建议的扩展:

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;    
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

public abstract class BaseSetupExtension
    implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

  @Override
  public void beforeAll(ExtensionContext context) throws Exception {
    // We need to use a unique key here, across all usages of this particular extension.
    String uniqueKey = this.getClass().getName();
    Object value = context.getRoot().getStore(GLOBAL).get(uniqueKey);
    if (value == null) {
      // First test container invocation.
      context.getRoot().getStore(GLOBAL).put(uniqueKey, this);
      setup();
    }
  }

  // Callback that is invoked <em>exactly once</em> 
  // before the start of <em>all</em> test containers.
  abstract void setup();

  // Callback that is invoked <em>exactly once</em> 
  // after the end of <em>all</em> test containers.
  // Inherited from {@code CloseableResource}
  public abstract void close() throws Throwable;
}

使用方法:

public class DemoSetupExtension extends BaseSetupExtension {
  @Override
  void setup() {}

  @Override
  public void close() throws Throwable {}
}  

@ExtendWith(DemoSetupExtension.class)
public class TestOne {
   @BeforeAll
   public void beforeAllTestOne { ... }

   @Test
   public void testOne { ... }
}

@ExtendWith(DemoSetupExtension.class)
public class TestTwo {
   @BeforeAll
   public void beforeAllTestTwo { ... }

   @Test
   public void testTwo { ... }
}

测试执行顺序为:

DemoSetupExtension.setup (*)
  TestOne.beforeAllTestOne
  TestOne.testOne
  TestOne.afterAllTestOne
  TestTwo.beforeAllTestTwo
  TestTwo.testTwo
  TestTwo.afterAllTestTwo
  DemoSetupExtension.close (*)

...无论您选择运行单个@测试(例如TestOne.testOne)、整个测试类(TestOne)还是多个/所有测试,这都将是真的。

yquaqz18

yquaqz184#

您可以使用定义staticBeforeAll的接口来标记使用数据库的每个测试类(这样它就不能被覆盖)。例如:

interface UsesDatabase {
    @BeforeAll
    static void initializeDatabaseConnections() {
        // initialize database connections
    }
}

对于每个实现类,此方法将被调用一次,因此您需要定义一种方法,以便仅初始化连接一次,然后不对其他调用执行任何操作。

gwo2fgha

gwo2fgha5#

我不知道有什么办法能做到这一点。
我只需要确保@BeforeAll的所有代码都调用某个单例来使init工作(可能是以一种懒惰的方式来避免重复)。
可能不太方便......我看到的唯一其他选择是:我假设您的测试在一个特定的JVM作业中运行。您 * 可以 * 将一个 * agent * 挂接到JVM运行中,这样就可以为您执行init工作。
除此之外:这两个建议听起来都像是在耍花招。在我看来,真实的的答案是:退一步,仔细检查环境的依赖关系。2然后找到一种方法来准备您的环境,使您的测试能够自动进行,并且“正确的事情”能够自动发生。3换句话说:请考虑调查一下导致此问题的体系结构。

1zmg4dgp

1zmg4dgp6#

上面建议做而不是对我起作用,所以我这样解决了这个问题:
将以下代码添加到您的Base抽象类(我指的是您在 setUpDriver() 方法中初始化驱动程序的抽象类):

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

现在,如果您的测试类将extendsfrom Base abstract class -〉setUpDriver() 方法将在first @Test only之前执行ONE次,每次项目运行。

pgccezyw

pgccezyw7#

以下是我对@菲利普Gayret给出的非常好的答案的POC改进,它追随了@Mihnea Giurgea的脚步。
我的子问题:***如何访问该共享单例资源?***(也许您也想知道...)

***边栏:在我的实验中,我发现使用多个@ExtendWith(...)似乎可以正确地嵌套。即使如此,我记得在我摸索的过程中,它在某些时候不是这样工作的,所以您应该***确保您的用例正确地工作[原文如此]。

因为您可能很赶时间,* 先吃甜点:* 下面是运行“文件夹中的所有测试”的输出:

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Colors::blue - resource=Something nice to share!
Colors::gold - resource=Something nice to share!
Base::afterAll
Base::beforeAll
Numbers::one - resource=Something nice to share!
Numbers::tre - resource=Something nice to share!
Numbers::two - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

当然,只要运行一个测试类就可以:

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Numbers::one - resource=Something nice to share!
Numbers::tre - resource=Something nice to share!
Numbers::two - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

而一个具体的考验,正如现在可以预料的那样:

NestedSingleton::beforeAll (setting resource)
Singleton::Start-Once
Base::beforeAll
Colors::gold - resource=Something nice to share!
Base::afterAll
Singleton::Finish-Once
NestedSingleton::close (clearing resource)

还在听我说吗?那你可能会喜欢看实际的代码...

======================================================
junitsingletonresource/Base.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith({Singleton.class})
public abstract class Base extends BaseNestedSingleton
{
    @BeforeAll public static void beforeAll() { System.out.println("Base::beforeAll"); }
    @AfterAll  public static void afterAll () { System.out.println("Base::afterAll" ); }
}

======================================================
junitsingletonresource/Colors.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.Test;

public class Colors extends Base
{
    @Test public void blue() { System.out.println("Colors::blue - resource=" + getResource()); }
    @Test public void gold() { System.out.println("Colors::gold - resource=" + getResource()); }
}

======================================================
junitsingletonresource/Numbers.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.Test;

public class Numbers extends Base
{
   @Test public void one() { System.out.println("Numbers::one - resource=" + getResource()); }
   @Test public void two() { System.out.println("Numbers::two - resource=" + getResource()); }
   @Test public void tre() { System.out.println("Numbers::tre - resource=" + getResource()); }
}

======================================================
junitsingletonresource/BaseNestedSingleton.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.extension.ExtensionContext;

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

/**
 * My riff on Phillip Gayret's solution from: https://stackoverflow.com/a/51556718/5957643
 */
@ExtendWith({BaseNestedSingleton.NestedSingleton.class})
public abstract class BaseNestedSingleton
{
    protected String getResource() { return NestedSingleton.getResource(); }

    static /*pkg*/ class NestedSingleton implements BeforeAllCallback, ExtensionContext.Store.CloseableResource
    {
        private static boolean initialized = false;
        private static String  resource    = "Tests should never see this value (e.g. could be null)";

        private static String getResource() { return resource; }

        @Override
        public void beforeAll(ExtensionContext context)
        {
            if (!initialized) {
                initialized = true;

                // The following line registers a callback hook when the root test context is shut down

                context.getRoot().getStore(GLOBAL).put(this.getClass().getCanonicalName(), this);

                // Your "before all tests" startup logic goes here, e.g. making connections,
                // loading in-memory DB, waiting for external resources to "warm up", etc.

                System.out.println("NestedSingleton::beforeAll (setting resource)");
                resource    = "Something nice to share!";
           }
        }

        @Override
        public void close() {
            if (!initialized) { throw new RuntimeException("Oops - this should never happen"); }

            // Cleanup the resource if needed, e.g. flush files, gracefully end connections, bury any corpses, etc.

            System.out.println("NestedSingleton::close (clearing resource)");
            resource = null;
        }
    }
}

======================================================
junitsingletonresource/Singleton.java
======================================================
package junitsingletonresource;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;

import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

/**
 * This is pretty much what Phillip Gayret provided, but with some printing for traceability
 */
public class Singleton implements BeforeAllCallback, ExtensionContext.Store.CloseableResource
{
    private static boolean started = false;

    @Override
    public void beforeAll(ExtensionContext context)
    {
        if (!started) {
            started = true;
            System.out.println("Singleton::Start-Once");
            context.getRoot().getStore(GLOBAL).put("any unique name", this);
        }
    }

    @Override
    public void close() { System.out.println("Singleton::Finish-Once"); }
}

相关问题