当中间件失败时,有没有办法让集成测试快速失败?

Is there a way to make integration tests fail quickly when middleware fails?

我们的测试环境有多种依赖中间件(CMS平台、底层DB、Elasticsearch索引)的集成测试。

它们是自动化的,我们使用 Docker 管理我们的中间件,因此我们不会遇到不可靠网络的问题。但是,有时我们的数据库会崩溃并且我们的测试会失败。

问题在于,此故障的检测是通过一连串的 org.hibernate.exception.JDBCConnectionException 消息进行的。这些都是通过超时来实现的。当这种情况发生时,我们最终会因为这个异常而导致数百个测试失败,每个测试都需要很多秒才能失败。因此,我们的测试需要 age 才能完成。事实上,当我们意识到它们已经完成时,我们通常只是手动杀死这些构建。

我的问题:在 Maven 驱动的 Java 测试环境中,有没有办法指示构建系统注意特定类型的异常并在它们到达(或到达)时终止整个进程某种门槛)?

我们可以监视我们的容器并以这种方式终止构建过程,但我希望有一种更干净的方法来使用 maven。

我不知道您是否可以对构建本身进行快速故障排除,或者甚至想这样做 - 因为构建的管理方面可能无法完成,但您可以这样做:

在所有依赖于数据库的测试 classes - 或者父 classes,因为像这样的东西是可继承的 - 添加:

@BeforeClass
public void testJdbc() throws Exception {
    Executors.newSingleThreadExecutor()
    .submit(new Callable() {
        public Object call() throws Exception {
            // execute the simplest SQL you can, eg. "SELECT 1"
            return null;
        }
    })
    .get(100, TimeUnit.MILLISECONDS);
}

如果 JDBC 简单查询在 100 毫秒内 return 失败,则整个测试 class 不会 运行 并将显示为 "fail"到构建。

让等待时间尽可能短,并且仍然可靠。

我知道这不是您想要的,但可以帮助 none 加快构建速度:

JUnit assumptions 允许在假设失败时让测试通过。您可以有一个像 assumeThat(db.isReachable()) 这样的假设,当达到超时时会跳过这些测试。

为了真正加快速度并且不要一遍又一遍地重复这个,你可以把它放在 @ClassRule:

A failing assumption in a @Before or @BeforeClass method will have the same effect as a failing assumption in each @Test method of the class.

当然你必须通过另一种方式将你的构建标记为不稳定,但这应该很容易做到。

如果您使用 TestNG 而不是 JUnit,则可以通过其他方式将测试定义为依赖于其他测试。

例如,像上面提到的其他人一样,您可以有一个方法来检查您的数据库连接并声明所有其他测试都依赖于此方法。

@Test
public void serverIsReachable() {}

@Test(dependsOnMethods = { "serverIsReachable" })
public void queryTestOne() {}

有了这个,如果 serverIsReachable 测试失败,将跳过依赖于此测试的所有其他测试并且 不会标记为失败 。跳过的方法将在最终报告中报告,这很重要,因为跳过的方法不一定是失败的。 但是 因为您的初始测试serverIsReachable 失败了,构建应该完全失败。 积极的影响是,您的其他测试都不会执行,这应该 非常快地失败

您还可以使用组扩展此逻辑。假设您之后的一些域逻辑测试使用了数据库查询,您可以用一个组声明每个数据库测试,例如

@Test(groups = { "jdbc" })
public void queryTestOne() {}

并声明您的域逻辑测试依赖于这些测试,

@Test(dependsOnGroups = { "jdbc.* })
public void domainTestOne() {}

因此,TestNG 将保证您的测试的执行顺序。

希望这有助于使您的测试更加结构化。有关更多信息,请查看 the TestNG dependency documentation.

您可以做的一件事是编写一个新的测试运行程序,如果发生此类错误,它将停止。这是一个可能看起来像的示例:

import org.junit.internal.AssumptionViolatedException;
import org.junit.runner.Description;
import org.junit.runner.notification.RunNotifier;
import org.junit.runners.BlockJUnit4ClassRunner;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;

public class StopAfterSpecialExceptionRunner extends BlockJUnit4ClassRunner {

    private boolean failedWithSpecialException = false;

    public StopAfterSpecialExceptionRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    @Override
    protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
        Description description = describeChild(method);
        if (failedWithSpecialException || isIgnored(method)) {
            notifier.fireTestIgnored(description);
        } else {
            runLeaf(methodBlock(method), description, notifier);
        }
    }

    @Override
    protected Statement methodBlock(FrameworkMethod method) {
        return new FeedbackIfSpecialExceptionOccurs(super.methodBlock(method));
    }

    private class FeedbackIfSpecialExceptionOccurs extends Statement {

        private final Statement next;

        public FeedbackIfSpecialExceptionOccurs(Statement next) {
            super();
            this.next = next;
        }

        @Override
        public void evaluate() throws Throwable {
            boolean complete = false;
            try {
                next.evaluate();
                complete = true;
            } catch (AssumptionViolatedException e) {
                throw e;
            } catch (SpecialException e) {
                StopAfterSpecialExceptionRunner.this.failedWithSpecialException = true;
                throw e;
            }
        }
    }
}

然后用 @RunWith(StopAfterSpecialExceptionRunner.class).

注释你的测试 类

基本上它所做的是检查某个异常(这里是 SpecialException,我自己写的一个异常),如果发生这种情况,它会导致抛出该异常的测试失败并跳过所有后续测试。如果愿意,您当然可以将其限制为使用特定注释进行注释的测试。

也有可能,类似的行为可以通过 Rule 实现,如果是这样,可能会更干净。