java—在JUnit5中,如何在所有测试之前运行代码

5tmbdcev  于 2021-06-30  发布在  Java
关注(0)|答案(6)|浏览(609)

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

eimct9ow

eimct9ow1#

在junit5中,现在可以通过创建一个自定义扩展来实现这一点,从中可以在根测试上下文上注册一个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})

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

js5cn81o

js5cn81o2#

以上建议不适用于我,所以我这样解决了这个问题:
向基本抽象类(我是指在setupdriver()方法中初始化驱动程序的抽象类)添加以下代码部分:

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

现在,如果您的测试类将从基抽象类扩展->setupdriver()方法将在第一次@test之前执行,每次项目运行只执行一次。

soat7uwm

soat7uwm3#

我不知道有什么办法做那件事。
我只想确保@beforeall的所有代码都调用某个单例来使init工作(可能是以一种懒惰的方式来避免重复)。
可能不方便。。。我看到的另一个选择是:我假设您的测试在特定的jvm作业中运行。您可以将一个代理挂接到jvm运行中,这样init就可以为您工作。
除此之外:这两个建议在某种程度上听起来像是一个黑客。在我看来,真正的答案是:后退一步,仔细检查您的环境对它的依赖关系。然后找到一种方法来准备您的环境,这样您的测试就会出现并且“正确的事情”会自动发生。换句话说:考虑一下为您带来这个问题的体系结构。

vxqlmq5t

vxqlmq5t4#

以下是我对@phillip gayret非常好的回答的poc改进,跟随@mihnea giurgea的脚步。
我的子问题:如何访问共享的单例资源(也许你也在想……)
侧边栏:在我的实验中,我发现使用多个 @ExtendWith(...) 似乎窝得很好。即便如此,我记得在我摸索的某个时候,它并不是这样工作的,所以你应该确保你的用例是正确工作的[sic]。
因为您可能很着急,所以沙漠是第一位的:下面是运行“文件夹中的所有测试”的输出:

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)

还跟我在一起吗?然后你可能会喜欢看到实际的代码。。。

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
BhRVTH5F6KBGi2PoOqARq1A6Q9Q8UqyCG98VXJFYBjupHzXYkN3Am6sxrAXTzKecEsJzZLVQ+2Zw
4AnitbANtCbmdL7IzoHPN0x2k1HrM2457ZVO4pPhu7jYNwskDn2XcsiSxun0LOjJNMypQSaOvZhL
DLBcLSw+iI88t9Fhd88mLsKAJ+m/G5R1s7TmSVzD51Z72eDbHwWawHcSX5Nx8Gg3nEcjWwtjUMfp
0Vj1gszCU8Gqdv04lMuZNeISl724pWpdDaVU6MZQ8duKJc/5BviVPlIcFi0rKvSGT61xwfpcrXsp
1TECScHo7F9l2mO+7N6eaXQswzbaRr8AUEsDBBQAAgAIAElomVEKdVo8pgAAACwBAAAiABwAanVu
aXRzaW5nbGV0b25yZXNvdXJjZS9Db2xvcnMuamF2YVVUCQADKinmX8Mt5l91eAsAAQToAwAABOgD
AACFzlEKwjAMBuD3niLsaUPsATYE0RuoF+i2UKpdU5pUlLG7u+l8FPMQCHz8f6LpbsYiXHNwwi5Y
j0IhIVNOHTZKuSFSEqBk9dvMOzrBpE10+oIss4m59a6DzhtmOJKnxIAPwdAzHAyjGhXMs184rPhO
rofWZywrGOH8ZMFBUxYdkwviQ1l8gup6QbCF70+7AjZgUU7rXVZVA9OPAku+/1uwoP8Fk3oBUEsB
Ah4DCgAAAAAAdWuZUQAAAAAAAAAAAAAAABcAGAAAAAAAAAAQAO1BAAAAAGp1bml0c2luZ2xldG9u
cmVzb3VyY2UvVVQFAAMdL+ZfdXgLAAEE6AMAAAToAwAAUEsBAh4DFAACAAgAi2mZUVgMlU6OAQAA
RgMAACUAGAAAAAAAAQAAAKSBUQAAAGp1bml0c2luZ2xldG9ucmVzb3VyY2UvU2luZ2xldG9uLmph
dmFVVAUAA4Ur5l91eAsAAQToAwAABOgDAABQSwECHgMUAAIACACLaZlRuVSfmtEAAACwAQAAIAAY
AAAAAAABAAAApIE+AgAAanVuaXRzaW5nbGV0b25yZXNvdXJjZS9CYXNlLmphdmFVVAUAA4Ur5l91
eAsAAQToAwAABOgDAABQSwECHgMUAAIACACZaJlRQ0JBcawAAACIAQAAIwAYAAAAAAABAAAApIFp
AwAAanVuaXRzaW5nbGV0b25yZXNvdXJjZS9OdW1iZXJzLmphdmFVVAUAA8Ip5l91eAsAAQToAwAA
BOgDAABQSwECHgMUAAIACAB1a5lRfzYBlEEDAADNBwAALwAYAAAAAAABAAAApIFyBAAAanVuaXRz
aW5nbGV0b25yZXNvdXJjZS9CYXNlTmVzdGVkU2luZ2xldG9uLmphdmFVVAUAAx0v5l91eAsAAQTo
AwAABOgDAABQSwECHgMUAAIACABJaJlRCnVaPKYAAAAsAQAAIgAYAAAAAAABAAAApIEcCAAAanVu
aXRzaW5nbGV0b25yZXNvdXJjZS9Db2xvcnMuamF2YVVUBQADKinmX3V4CwABBOgDAAAE6AMAAFBL
BQYAAAAABgAGAHQCAAAeCQAAAAA=

哈哈-我的小笑话。。。这些东西实际上是代码:base64编码的zip创建为:

+ zip -9 -r junitsingletonresource{.zip,}
updating: junitsingletonresource/ (stored 0%)
updating: junitsingletonresource/Singleton.java (deflated 55%)
updating: junitsingletonresource/Base.java (deflated 54%)
updating: junitsingletonresource/Numbers.java (deflated 60%)
updating: junitsingletonresource/BaseNestedSingleton.java (deflated 62%)
updating: junitsingletonresource/Colors.java (deflated 50%)

+ base64 < junitsingletonresource.zip > junitsingletonresource.zip.b64

+ cat junitsingletonresource.zip.b64  # OMG! It's like the Matrix...

尽管如此,您还是应该怀疑正在展开该数据块。它可能是一个有点硬币钱包偷或os砖。谁知道呢(我应该把这些都推到一个github上,但我太懒了。无论如何……)
真正的快乐(tm):

======================================================
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"); }
}
lf5gs5x2

lf5gs5x25#

根据@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 (*)

…无论您选择运行单个@test(例如testone.testone),还是整个测试类(testone),或多个/所有测试,这都是正确的。

ars1skjm

ars1skjm6#

您可以用一个定义
static BeforeAll (这样它就不能被覆盖)。例如。:

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

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

相关问题