2

为了只创建一次环境并避免继承,我定义了一个 JUnit Suite 类,其中包含@ClassRule

@RunWith(Suite.class)               
@Suite.SuiteClasses({               
  SuiteTest1.class              
})      

public class JUnitTest {

    @ClassRule
    private static DockerComposeContainer env = ...


    @BeforeClass
    public static void init(){
        ...
    }

    ...

}

还有一个在测试方法中使用 env 的测试类:

public class SuiteTest1 {               

    @Test
    public void method(){
        client.query(...);// Executes a query against docker container


    }
}

当我通过运行测试套件执行测试时,一切都按预期工作。但是,当我直接尝试运行(即使使用 IDE)SuiteTest1测试类时,它会失败并且不会调用套件中的任何内容(即@ClassRule@BeforeClass)。

关于如何以良好的方式实现 SuiteTest1 单次执行的任何建议(无需JUnitTest从内部调用静态方法SuiteTest1)?

4

1 回答 1

2

重新表述这个问题:您想要一个带有 before-all 和 after-all 钩子的 JUnit 套件,它也将在逐个运行测试时运行(例如,从 IDE 运行)。

AFAIK JUnit 4 没有为此提供任何开箱即用的功能,但是如果您可以将一些 Spring 第三方部门(spring-testspring-context)合并到您的项目中,我可以提出一个解决方法使用。

可以在此处找到本文后面的完整代码示例。

解决方案(使用 Spring)

我们将使用 Spring 上下文来实现我们的初始化和清理。让我们为我们的测试添加一个基类:

@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {
    
    @ClassRule
    public final static SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    public static class ContextInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            System.out.println("Initializing context");

            context.addApplicationListener(
                    (ApplicationListener<ContextClosedEvent>)
                            contextClosedEvent ->
                                    System.out.println("Closing context"));
        }
    }
}

请注意使用 Spring-superpowers 增强我们的基类的 JUnit 规则(Spring 测试注释处理 -SpringClassRule在这种情况下,但还有更多好东西 - 请参阅Spring 测试参考了解详细信息)。您可以将其用于此目的,但它的灵活性要低得多(因此省略)。SpringMethodRuleContextConfigurationSpringRunner

测试类:

public class TestClass1 extends AbstractTestClass {
    
    @Test
    public void test() {
        System.out.println("TestClass1 test");
    }
}

public class TestClass2 extends AbstractTestClass {
    
    @Test
    public void test() {
        System.out.println("TestClass2 test");
    }
}

和测试套件:

@RunWith(Suite.class)
@SuiteClasses({TestClass1.class, TestClass2.class})
public class TestSuite {
}

运行套件时的输出(为简洁起见,删除了特定于 Spring 的日志):

Initializing context
TestClass1 test
TestClass2 test
Closing context

运行单个测试 ( TestClass1) 时的输出:

Initializing context
TestClass1 test
Closing context

一句话解释

这种工作方式是因为 Spring 的上下文缓存。从文档中引用:

一旦 TestContext 框架为一个测试加载了一个ApplicationContext(or WebApplicationContext),该上下文就会被缓存并重用于在同一测试套件中声明相同唯一上下文配置的所有后续测试。要了解缓存的工作原理,请务必了解“唯一”和“测试套件”的含义。</p>

-- https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/testing.html#testcontext-ctx-management-caching

ContextConfiguration请注意,如果您为层次结构中的任何类(TestClass1TestClass2在我们的示例中)覆盖上下文配置(例如添加另一个上下文初始化器),您将获得另一个上下文(和另一个初始化)。

使用 bean 共享实例

您可以在上下文中定义 bean。它们将在使用相同上下文的所有测试中共享。这对于在测试套件中共享对象(根据标签判断为您的案例中的 Testcontainers 容器)非常有用。

让我们添加一个 bean:

@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {

    @ClassRule
    public final static SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    public static class ContextInitializer
            implements ApplicationContextInitializer<ConfigurableApplicationContext> {
        @Override
        public void initialize(ConfigurableApplicationContext context) {
            ADockerContainer aDockerContainer = new ADockerContainer();
            aDockerContainer.start();

            context.getBeanFactory().registerResolvableDependency(
                    ADockerContainer.class, aDockerContainer);

            context.addApplicationListener(
                    (ApplicationListener<ContextClosedEvent>)
                            contextClosedEvent ->
                                    aDockerContainer.stop());
        }
    }
}

并将其注入测试类:

public class TestClass1 extends AbstractTestClass {
    
    @Autowired
    private ADockerContainer aDockerContainer;

    @Test
    public void test() {
        System.out.println("TestClass1 test " + aDockerContainer.getData());
    }
}

public class TestClass2 extends AbstractTestClass {
    
    @Autowired
    private ADockerContainer aDockerContainer;

    @Test
    public void test() {
        System.out.println("TestClass2 test " + aDockerContainer.getData());
    }
}

ADockerContainer班级:

public class ADockerContainer {
    private UUID data;

    public void start() {
        System.out.println("Start container");
        data = UUID.randomUUID();
    }

    public void stop() {
        System.out.println("Stop container");
    }

    public String getData() {
        return data.toString();
    }
}

(示例)输出:

Start container
TestClass1 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
TestClass2 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
Stop container
于 2018-11-14T22:58:18.777 回答