测试失败时记录异常的最佳方法(例如使用 junit 规则)

发布于 2024-12-05 12:31:40 字数 587 浏览 1 评论 0原文

当我运行完整的测试套件时,如果导致测试失败的异常出现在我的 (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 块中捕获异常。是否有一种解决方法可以以类似简单且通用的方式实现这一目标?


顺便说一句,我现在正在做的

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

When I'm running a complete test suite, it would be helpful if exceptions that caused a test to fail would appear in my (SLF4J-)log. What is the best method to achieve this?

What I would like

is a junit4 rule that handles exception logging for me. The code

@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;
        }
    }
}

of course does not work, since I can only catch exceptions out of a try block. Is there a workaround to somehow achieve this in a similarly simple and general way?


BTW, what I'm doing right now

is logging the exception the moment it is created. But it would be nicer to log exceptions at the interface between caller and the library, so in my case in the test case. Not logging when the exceptions are created would also guarantee that they don't show up multiple times when the caller decides to log them.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

娇女薄笑 2024-12-12 12:31:40

您需要扩展 TestRule,特别是 apply()。例如,请查看 org.junit.rules.ExternalResource & org.junit.rules.TemporaryFolder。

ExternalResource 看起来像这样:

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,而 ExceptionLoggingRule 又调用 testMe 方法。 ExceptionLoggingRule 捕获异常,记录它并重新抛出它,然后由 ExpectedException 处理。

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

    @Rule public ExpectedException expectedException = ExpectedException.none();
    @Rule public ExceptionLoggingRule exceptionLoggingRule = new 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

You need to extend TestRule, in particular the apply(). For an example, have a look at org.junit.rules.ExternalResource & org.junit.rules.TemporaryFolder.

ExternalResource looks like this:

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 then extends this and implements before() and 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
    }

So the before gets called before the testMethod, and the after is called in the finally, but you can catch and log any Exception, like:

    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();
                }
            }
        };
    }

EDIT: The following works:

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");
    }
}

The test passes and you get the following output:

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)
...

The order that the rules are applied is ExpectedException which calls ExceptionLoggingRule which calls the testMe method. The ExceptionLoggingRule catches the Exception, logs it and rethrows it, and it is then processed by ExpectedException.

If you want to log only unexpected exceptions, you just switch the declaration order of the rules:

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

That way, expectedException is applied first (i.e. nested in exceptionLoggingRule), and only rethrows exceptions that are not expected. Furthermore, if some exception was expected and none occured, expectedException will throw an AssertionError which will also get logged.

This evaluation order isn't guaranteed, but it is quite unlikely to vary unless you're playing with very different JVMs, or inheriting between Test classes.

If the evaluation order is important, then you can always pass one rule to the other for evaluation.

EDIT: With the recently released Junit 4.10, you can use @RuleChain to chain rules correctly:

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);
   }
}

writes the log

starting outer rule
starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule
下雨或天晴 2024-12-12 12:31:40

跳出框框...您使用什么来运行测试?大多数运行测试的环境(例如,Ant、Jenkins、Maven 等)都具有使用输出 XML 文件的测试运行器的方法,并支持将套件中的 XML 文件聚合到综合报告中。

Look outside the box... What are you using to run the tests? Most environments for running tests (e.g., Ant, Jenkins, Maven, etc.) have means for using a testrunner that outputs an XML file, with support for aggregating the XML files from a suite into a comprehensive report.

姐不稀罕 2024-12-12 12:31:40

这看起来很简单,我认为我错了,你问的是不同的东西,但也许我可以提供帮助:

JUnit 4.X

@Test(expected=Exception.class)

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

This seems so easy that I think I am wrong and you are asking something different, but maybe I can help:

JUnit 4.X

@Test(expected=Exception.class)

is going to pass the test if the exception is thrown in the test, or fail with a message captured by the Junit framework

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文