【问题标题】:In JUnit 5, how to run code before all tests在 JUnit 5 中,如何在所有测试之前运行代码
【发布时间】:2017-09-03 02:44:38
【问题描述】:

@BeforeAll 注释标记了一个方法,该方法要在一个中的所有测试之前运行。

http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

但是有没有办法在所有类的all 测试之前运行一些代码?

我想确保测试使用一组特定的数据库连接,并且这些连接的全局一次性设置必须发生在运行任何测试之前。 p>

【问题讨论】:

  • 仅作记录:如果您发现其中一个答案足够有用 - 请考虑在某个时候接受其中一个 ;-)

标签: java junit junit5


【解决方案1】:

现在可以通过创建自定义扩展在 JUnit5 中实现,您可以在根测试上下文中注册关闭挂钩。

您的扩展程序将如下所示;

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

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

【讨论】:

  • 如果您需要为每个测试类执行此操作,那么您还可以使用 automatic extension regristration 使用 ServiceLoader 注册扩展。然后你就不需要注释每一个测试(你可能会错过一些风险)。
  • 在我的情况下,来自CloseableResourceclose 未被调用,但来自AfterAllCallbackafterAll 被调用
  • FWIW 我用 Spring 实现了这个以使用 DataSource bean 设置测试数据,它工作得很好; stackoverflow.com/a/62504238/278800 中的详细信息。没有您的帮助,我无法做到这一点,谢谢!
  • 我忍不住,不得不发布一个华丽的“走得更远”的答案。如果您认为它增加了 convo,请投票:stackoverflow.com/a/65450782/5957643
  • extended提供的样本在并行执行以及上面提到的自动扩展注册中都是有效的。
【解决方案2】:

@Philipp Gayret 已经提供的答案在并行(即junit.jupiter.execution.parallel.enabled = true)中测试JUnit 时存在一些问题。

因此我将解决方案调整为:

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 {

    private static boolean started = false;
    // Gate keeper to prevent multiple Threads within the same routine
    final static Lock lock = new ReentrantLock();

    @Override
    public void beforeAll(final ExtensionContext context) throws Exception {
        // lock the access so only one Thread has access to it
        lock.lock();
        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
        }
        // free the access
        lock.unlock();
    }

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

如下所述,JUnit5 提供了一个自动的Extension Registration。为此,在src/test/resources/ 中添加一个名为/META-INF/services 的目录并添加一个名为org.junit.jupiter.api.extension.Extension 的文件。在此文件中添加您的班级的完全分类名称,例如

at.myPackage.BeforeAllTestsExtension

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

junit.jupiter.extensions.autodetection.enabled=true

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

【讨论】:

  • 完美运行!提示:扩展类必须是公共的,就像这个例子中一样
【解决方案3】:

根据@Philipp 的建议,这里有一个更完整的代码 sn-p:

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

...无论您是否选择运行单个 @Test(例如 TestOne.testOne),或整个测试类 (TestOne),或多个/所有测试。

【讨论】:

【解决方案4】:

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

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

每个实现类都会调用一次此方法,因此您需要定义一种方法来只初始化一次连接,然后对其他调用不执行任何操作。

【讨论】:

    【解决方案5】:

    我不知道这样做的意思。

    我会简单地确保 @BeforeAll 的所有代码调用某个单例以使该 init 工作(可能以一种懒惰的方式避免重复)。

    可能不方便......我看到的唯一其他选择:我假设您的测试在特定的 JVM 作业中运行。您可以agent 挂接到该 JVM 运行中,该初始化为您工作。

    除此之外:这两个建议对我来说听起来都像是一种黑客行为。在我看来,真正的答案是:退后一步,仔细检查你的环境的依赖关系。然后找到一种方法来准备您的环境,使您的测试出现并且“正确的事情”自动发生。换句话说:考虑调查给你带来这个问题的架构。

    【讨论】:

      【解决方案6】:

      以上建议对我有用,所以我这样解决了这个问题:

      将这部分代码添加到您的 Base 抽象类(我的意思是您在 setUpDriver() 方法中初始化驱动程序的抽象类):

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

      现在,如果您的测试类将从基础抽象类扩展 -> setUpDriver() 方法将在第一个 @Test 之前执行 ONE 每个项目运行的时间。

      【讨论】:

        【解决方案7】:

        这是我对 @Phillip 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"); }
        }
        
        

        【讨论】:

          猜你喜欢
          • 2020-10-24
          • 2021-07-22
          • 1970-01-01
          • 1970-01-01
          • 2015-05-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多