【问题标题】:Best way of logging exceptions when tests fail (e.g. using a junit rule)测试失败时记录异常的最佳方式(例如,使用 junit 规则)
【发布时间】:2011-11-22 03:05:21
【问题描述】:

当我运行一个完整的测试套件时,如果导致测试失败的异常出现在我的 (SLF4J-) 日志中会很有帮助。实现这一目标的最佳方法是什么?

我想要什么

是一个 junit4 规则,它为我处理异常日志记录。代码

@Rule
public TestRule logException = new TestWatcher() {
    @Override
    public void failed(Description d) {
        catch (Exception e) {
            logger.error("Test ({}) failed because of exception {}", d, e);
            throw e;
        }
    }
}

当然不起作用,因为我只能从 try 块中捕获异常。是否有一种解决方法可以以同样简单和通用的方式以某种方式实现这一目标?


顺便说一句,我现在在做什么

在创建异常时记录它。但是在调用者和库之间的接口处记录异常会更好,所以在我的例子中是测试用例。在创建异常时不记录也可以保证在调用者决定记录它们时它们不会多次出现。

【问题讨论】:

  • “如果导致测试失败的异常出现在我的日志中会很有帮助”。你能解释一下为什么吗?这似乎是一个奇怪的要求。测试运行器报告(广义上的“日志”)所有因抛出异常而失败的测试用例。
  • 我想要一个包含所有内容的日志。这样一来,我就可以按时间顺序将所有内容集中在一个地方(可以搜索和过滤一个文件,无需合并)。

标签: java exception logging junit junit-rule


【解决方案1】:

这似乎很容易,以至于我认为我错了,而您问的是不同的问题,但也许我可以提供帮助:

JUnit 4.X

@Test(expected=Exception.class)

如果在测试中抛出异常,将通过测试,或者失败并由 Junit 框架捕获消息

【讨论】:

  • 使用期望的参数有利于测试异常。我想要的是记录意外异常的通用解决方案。看看 JUnit 4.X 规则;他们在所有测试方法中提供这些一般行为。 Matthew 在他的回复中展示了如何实现异常日志记录规则以及如何将其与其他规则兼容地链接起来。
【解决方案2】:

您需要扩展 TestRule,尤其是 apply()。例如,查看 org.junit.rules.ExternalResource 和 org.junit.rules.TemporaryFolder。

外部资源如下所示:

public abstract class ExternalResource implements TestRule {
    public Statement apply(Statement base, Description description) {
        return statement(base);
    }

    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } finally {
                    after();
                }
            }
        };
    }

    /**
     * Override to set up your specific external resource.
     * @throws if setup fails (which will disable {@code after}
     */
    protected void before() throws Throwable {
        // do nothing
    }

    /**
     * Override to tear down your specific external resource.
     */
    protected void after() {
        // do nothing
    }
}

TemporaryFolder 然后扩展它并实现 before() 和 after()。

public class TemporaryFolder extends ExternalResource {
    private File folder;

    @Override
    protected void before() throws Throwable {
        // create the folder
    }

    @Override
    protected void after() {
        // delete the folder
    }

所以 before 在 testMethod 之前被调用,而 after 在 finally 中被调用,但是你可以捕获并记录任何异常,例如:

    private Statement statement(final Statement base) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                before();
                try {
                    base.evaluate();
                } catch (Exception e) {
                    log.error("caught Exception", e);
                } finally {
                    after();
                }
            }
        };
    }

编辑:以下作品:

public class SoTest {
    public class ExceptionLoggingRule implements TestRule {
        public Statement apply(Statement base, Description description) {
            return statement(base);
        }

        private Statement statement(final Statement base) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    try {
                        base.evaluate();
                    } catch (Exception e) {
                        System.out.println("caught an exception");
                        e.printStackTrace(System.out);
                        throw e;
                    }
                }
            };
        }
    }

    @Rule public ExceptionLoggingRule exceptionLoggingRule = new ExceptionLoggingRule();
    @Rule public ExpectedException expectedException = ExpectedException.none();

    @Test
    public void testMe() throws Exception {
        expectedException.expect(IOException.class);
        throw new IOException("here we are");
    }
}

测试通过,你会得到以下输出:

caught an exception
java.io.IOException: here we are
    at uk.co.farwell.junit.SoTest.testMe(SoTest.java:40)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
...

应用规则的顺序是 ExpectedException 调用 ExceptionLoggingRule 调用 testMe 方法。 ExceptionLoggingRule 捕获异常,记录并重新抛出,然后由 ExpectedException 处理。

如果你只想记录意外的异常,你只需切换规则的声明顺序:

    @Rule public ExpectedException expectedException = ExpectedException.none();
    @Rule public ExceptionLoggingRule exceptionLoggingRule = new ExceptionLoggingRule();

这样,expectedException 被首先应用(即嵌套在 exceptionLoggingRule 中),并且只重新抛出不期望的异常。此外,如果某些异常是预期的并且没有发生,则 expectedException 将抛出一个 AssertionError 也将被记录。

无法保证此评估顺序,但除非您使用非常不同的 JVM,或在测试类之间进行继承,否则它不太可能发生变化。

如果评估顺序很重要,那么您始终可以将一条规则传递给另一条规则进行评估。

编辑:使用最近发布的 Junit 4.10,您可以使用 @RuleChain 正确链接规则:

public static class UseRuleChain {
   @Rule
   public TestRule chain= RuleChain
                          .outerRule(new LoggingRule("outer rule")
                          .around(new LoggingRule("middle rule")
                          .around(new LoggingRule("inner rule");

   @Test
   public void example() {
           assertTrue(true);
   }
}

写日志

starting outer rule
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule

【讨论】:

  • 嗯,想想看:这是否与 ExpectedException 规则和 @Test(expected=ExceptionName.class) 兼容?以什么顺序调用 rules/Statements.evaluate()?如果在 ExpectedException 规则中调用了这个 ExceptionLoggingRule,我可以为 ExceptionLoggingRule 重新抛出异常。所以所有异常都会被记录:(但它会起作用。如果在 junit-framework 评估测试是通过还是失败之后有办法访问异常会更好(正如我在代码示例中尝试的那样)。你知道这样的方式吗?
  • 添加了示例实现。
  • 非常感谢 :) 您能否提供一个参考,评估顺序是在哪里/如何决定的? (stackoverflow.com/questions/2730365/… 表示第一个声明的规则不会在第二个声明的规则之前执行)。
  • 事实上没有参考,但是当我有时间提出问题时会有参考。我们应该更改 @Rule 的 javadoc 中的描述。就像我说的,ExpectedException 调用了调用 testMe() 的 ExceptionLoggingRule(通过调用 base.evaluate())。所以 ExpectedException 有机会先执行。
  • @RuleChain 现在允许您在 JUnit 4.10 中正确链接规则
【解决方案3】:

跳出框框...您使用什么来运行测试?大多数运行测试的环境(例如,Ant、Jenkins、Maven 等)都可以使用输出 XML 文件的测试运行程序,并支持将 XML 文件从套件聚合成综合报告。

【讨论】:

  • 我在 Ecipe(和 Infinitest)或 Ant 中运行我的测试套件。由于我经常查看在整个测试套件中累积的日志文件,因此我确实需要在其中报告异常。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 1970-01-01
  • 2014-04-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多