我们的测试环境有各种依赖中间件的集成测试(CMS平台、底层数据库、Elasticsearch索引)。
它们是自动化的,我们使用Docker管理中间件,因此我们不会遇到不可靠网络的问题。然而,有时我们的数据库会崩溃,我们的测试也会失败。
问题是通过一连串的org.hibernate.exception.JDBCConnectionException
消息来检测此故障。这些都是通过超时来实现的。当这种情况发生时,除了这个例外,我们最终会有数百个测试失败,每个测试都需要几秒钟才能失败。因此,我们的测试需要年龄才能完成。事实上,当我们意识到这些构建已经完成时,我们通常只是手动终止它们。
我的问题是:在Maven驱动的Java测试环境中,是否有一种方法可以指导构建系统注意特定类型的异常,并在它们到达(或达到某种阈值)时终止整个过程?
我们可以监视我们的容器并以这种方式终止构建过程,但我希望有一种更干净的方法来使用maven。
如果您使用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将保证测试的执行顺序。
希望这能帮助你的测试更有条理。有关更多信息,请查看TestNG依赖文档。
我意识到这并不完全是你所要求的,但可以帮助加快构建:
JUnit假设允许在假设失败时让测试通过。您可以有一个类似assumeThat(db.isReachable())
的假设,当达到超时时会跳过这些测试。
为了真正加快速度,并且不重复这个过程,你可以把它放在@ClassRule
:中
@Before或@BeforeClass方法中的失败假设与该类的每个@Test方法中的错误假设具有相同的效果。
当然,您必须通过另一种方式将构建标记为不稳定,但这应该很容易实现。
我不知道你是否可以快速失败构建本身,甚至想失败——因为构建的管理方面可能还没有完成,但你可以这样做:
在所有依赖于数据库或父类的测试类中,因为类似的东西是可继承的,请添加以下内容:
@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简单查询未能在100ms内返回,那么整个测试类将不会运行,并且将显示为构建的"失败"。
尽可能缩短等待时间,并且仍然可靠。
您可以做的一件事是编写一个新的测试运行程序,如果发生此类错误,该程序将停止。下面是一个可能的例子:
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
也有可能实现类似的行为,如果是这样,可能会干净得多。