当前位置:  开发笔记 > 编程语言 > 正文

JUnit:可能'期望'包装异常?

如何解决《JUnit:可能'期望'包装异常?》经验,为你挑选了5个好方法。

我知道可以在JUnit中定义'预期'异常,执行:

@Test(expect=MyException.class)
public void someMethod() { ... }

但是,如果总是抛出相同的异常,但具有不同的"嵌套" 原因会怎样 .

有什么建议?



1> Rowan..:

从JUnit 4.11开始,您可以使用ExpectedException规则的expectCause()方法:

import static org.hamcrest.CoreMatchers.*;

// ...

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void throwsNestedException() throws Exception {
    expectedException.expectCause(isA(SomeNestedException.class));

    throw new ParentException("foo", new SomeNestedException("bar"));
}


因为hamcrest泛型地狱,第6行必须如下所示:`expectedException.expectCause(is(IsInstanceOf. instanceOf(SomeNestedException.class)));`除此之外,它是一个优雅的解决方案.
[相同文档](http://hamcrest.org/JavaHamcrest/javadoc/1.3/org/hamcrest/core/Is.html#isA%28java.lang.Class%29)告诉我们有一个匹配器`isA`作为简写`是(instanceOf(clazz))`,所以这就足够了:`expectedException.expectCause(isA(SomeNestedException.class));`

2> Paul Sonier..:

您可以将测试代码包装在try/catch块中,捕获抛出的异常,检查内部原因,log/assert/whatever,然后重新抛出异常(如果需要).


这个答案现在已经过时了.使用@ Rowan的答案

3> mtpettyp..:

如果您使用的是最新版本的JUnit,则可以扩展默认测试运行器以便为您处理(无需将每个方法包装在try/catch块中)

ExtendedTestRunner.java - 新的测试运行器:

public class ExtendedTestRunner extends BlockJUnit4ClassRunner
{
    public ExtendedTestRunner( Class clazz )
        throws InitializationError
    {
        super( clazz );
    }

    @Override
    protected Statement possiblyExpectingExceptions( FrameworkMethod method,
                                                     Object test,
                                                     Statement next )
    {
        ExtendedTest annotation = method.getAnnotation( ExtendedTest.class );
        return expectsCauseException( annotation ) ?
                new ExpectCauseException( next, getExpectedCauseException( annotation ) ) :
                super.possiblyExpectingExceptions( method, test, next );
    }

    @Override
    protected List computeTestMethods()
    {
        Set testMethods = new HashSet( super.computeTestMethods() );
        testMethods.addAll( getTestClass().getAnnotatedMethods( ExtendedTest.class ) );
        return testMethods;
    }

    @Override
    protected void validateTestMethods( List errors )
    {
        super.validateTestMethods( errors );
        validatePublicVoidNoArgMethods( ExtendedTest.class, false, errors );
    }

    private Class getExpectedCauseException( ExtendedTest annotation )
    {
        if (annotation == null || annotation.expectedCause() == ExtendedTest.None.class)
            return null;
        else
            return annotation.expectedCause();
    }

    private boolean expectsCauseException( ExtendedTest annotation) {
        return getExpectedCauseException(annotation) != null;
    }

}

ExtendedTest.java - 用于标记测试方法的注释:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface ExtendedTest
{

    /**
     * Default empty exception
     */
    static class None extends Throwable {
        private static final long serialVersionUID= 1L;
        private None() {
        }
    }

    Class expectedCause() default None.class;
}

ExpectCauseException.java - 新的JUnit语句:

public class ExpectCauseException extends Statement
{
    private Statement fNext;
    private final Class fExpected;

    public ExpectCauseException( Statement next, Class expected )
    {
        fNext= next;
        fExpected= expected;
    }

    @Override
    public void evaluate() throws Exception
    {
        boolean complete = false;
        try {
            fNext.evaluate();
            complete = true;
        } catch (Throwable e) {
            if ( e.getCause() == null || !fExpected.isAssignableFrom( e.getCause().getClass() ) )
            {
                String message = "Unexpected exception cause, expected<"
                            + fExpected.getName() + "> but was<"
                            + ( e.getCause() == null ? "none" : e.getCause().getClass().getName() ) + ">";
                throw new Exception(message, e);
            }
        }
        if (complete)
            throw new AssertionError( "Expected exception cause: "
                    + fExpected.getName());
    }
}

用法:

@RunWith( ExtendedTestRunner.class )
public class MyTests
{
    @ExtendedTest( expectedCause = MyException.class )
    public void someMethod()
    {
        throw new RuntimeException( new MyException() );
    }
}



4> GreenKiwi..:

您可以随时手动执行此操作:

@Test
public void someMethod() {
    try{
        ... all your code
    } catch (Exception e){
        // check your nested clauses
        if(e.getCause() instanceof FooException){
            // pass
        } else {
            Assert.fail("unexpected exception");
        }
    }



5> Jesse Merrim..:

您可以为异常创建匹配器.当你正在使用其他的测试运行像这样的作品即使的Arquillian的@RunWith(Arquillian.class),所以你不能使用@RunWith(ExtendedTestRunner.class)方法上面建议.

这是一个简单的例子:

public class ExceptionMatcher extends BaseMatcher {
    private Class[] classes;

    // @SafeVarargs // <-- Suppress warning in Java 7. This usage is safe.
    public ExceptionMatcher(Class... classes) {
        this.classes = classes;
    }

    @Override
    public boolean matches(Object item) {
        for (Class klass : classes) {
            if (! klass.isInstance(item)) {
                return false;
            }   

            item = ((Throwable) item).getCause();
        }   

        return true;
    }   

    @Override
    public void describeTo(Description descr) {
        descr.appendText("unexpected exception");
    }
}


然后将它与@Rule和ExpectedException一起使用,如下所示:

@Rule
public ExpectedException thrown = ExpectedException.none();

@Test
public void testSomething() {
    thrown.expect(new ExceptionMatcher(IllegalArgumentException.class, IllegalStateException.class));

    throw new IllegalArgumentException("foo", new IllegalStateException("bar"));
}

由Craig Ringer于2012年添加编辑:增强且更可靠的版本:

基本用法与上述不变

可以传递可选的第一个参数boolean rethrow来抛出不匹配的异常.这样可以保留嵌套异常的堆栈跟踪,以便于调试.

使用Apache Commons Lang ExceptionUtils来处理cause循环并处理一些常见异常类使用的非标准异常嵌套.

自我描述包括可接受的例外

失败时自我描述包括遇到的异常的原因堆栈

处理Java 7警告.删除@SaveVarargs旧版本.

完整代码:

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;


public class ExceptionMatcher extends BaseMatcher {
    private Class[] acceptedClasses;

    private Throwable[] nestedExceptions;
    private final boolean rethrow;

    @SafeVarargs
    public ExceptionMatcher(Class... classes) {
        this(false, classes);
    }

    @SafeVarargs
    public ExceptionMatcher(boolean rethrow, Class... classes) {
        this.rethrow = rethrow;
        this.acceptedClasses = classes;
    }

    @Override
    public boolean matches(Object item) {
        nestedExceptions = ExceptionUtils.getThrowables((Throwable)item);
        for (Class acceptedClass : acceptedClasses) {
            for (Throwable nestedException : nestedExceptions) {
                if (acceptedClass.isInstance(nestedException)) {
                    return true;
                }
            }
        }
        if (rethrow) {
            throw new AssertionError(buildDescription(), (Throwable)item);
        }
        return false;
    }

    private String buildDescription() {
        StringBuilder sb = new StringBuilder();
        sb.append("Unexpected exception. Acceptable (possibly nested) exceptions are:");
        for (Class klass : acceptedClasses) {
            sb.append("\n  ");
            sb.append(klass.toString());
        }
        if (nestedExceptions != null) {
            sb.append("\nNested exceptions found were:");
            for (Throwable nestedException : nestedExceptions) {
                sb.append("\n  ");
                sb.append(nestedException.getClass().toString());
            }
        }
        return sb.toString();
    }

    @Override
    public void describeTo(Description description) {
        description.appendText(buildDescription());
    }

}


典型输出:

java.lang.AssertionError:  Expected: Unexpected exception. Acceptable (possibly nested) exceptions are:
   class some.application.Exception
Nested exceptions found were:
   class javax.ejb.EJBTransactionRolledbackException
   class javax.persistence.NoResultException
     got: 

推荐阅读
谢谢巷议
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有