@BeforeAll
注释标记了在类中的所有测试之前运行的方法。
http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations
但是在所有类中,有没有办法在所有测试之前运行一些代码?
我想确保测试使用一组特定的数据库连接,并且这些连接的全局一次性设置必须在 之前任何测试之前发生。
答案 0 :(得分:20)
现在可以通过创建自定义扩展在JUnit5中实现,您可以从中在根test-context上注册关闭钩子。
您的扩展程序应如下所示:
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})
在多个类上使用此扩展时,启动和关闭逻辑将仅被调用一次。
答案 1 :(得分:7)
在并行(即junit.jupiter.execution.parallel.enabled = true
)中测试 JUnit 时,@Philipp Gayret 已经提供的答案存在一些问题。
因此,我将解决方案调整为:
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
有了这个扩展程序会自动附加到您的所有测试中。
答案 2 :(得分:5)
目前不支持此功能,但有关此主题的JUnit 5拉取请求:Introduce support for before/after callbacks once per entire test run。
答案 3 :(得分:3)
您可以使用定义static
BeforeAll
的接口标记使用数据库的每个测试类(以便不能覆盖它)。 e.g:
interface UsesDatabase {
@BeforeAll
static void initializeDatabaseConnections() {
// initialize database connections
}
}
此方法将针对每个实现类调用一次,因此您需要定义一种方法来初始化您的连接只有一次,然后对其他调用不执行任何操作。
答案 4 :(得分:3)
根据@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)或多个/所有测试。
答案 5 :(得分:1)
我不知道这样做的意思。
我只是确保@BeforeAll的所有代码都调用某个单例以使该init工作(可能以一种懒惰的方式避免重复)。
可能不方便...我看到的唯一其他选项:我假设您的测试在特定的JVM作业中运行。您可以将 agent 挂钩到该JVM运行中,这对您来说是初始化的。
除此之外:这两个建议听起来都像是对我的黑客攻击。我眼中的真实答案:退后一步,仔细检查您的环境中的依赖关系。然后找到一种方法准备您的环境,使您的测试出现并自动发生“正确的事情”。换句话说:考虑一下为你解决这个问题的架构。
答案 6 :(得分:1)
以上建议对我来说不有用,所以我解决了这个问题:
将这部分代码添加到您的基本抽象类中(我的意思是在 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 :(得分:-1)
这是我对 @Phillip Gayret 提供的非常好的答案的 POC 改进,跟随 @Mihnea Giurgea 的脚步。
我的子问题:如何访问共享的单例资源?(也许你也想知道这个......)
侧边栏: 在我的实验中,我发现使用多个 @ExtendWith(...)
似乎可以正确嵌套。即便如此,我记得它在我摸索过程中的某些时候并没有那样工作,所以你应该确保你的用例正常工作[原文如此]。< /p>
因为您可能很着急,所以甜点在先:这是运行“文件夹内的所有测试”的输出:
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"); }
}