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