如何测试代码不会抛出异常?

442

我知道完成它的一种方法是:

@Test
public void foo() {
   try {
      // execute code that you expect not to throw Exceptions.
   } catch(Exception e) {
      fail("Should not have thrown any exception");
   }
}

有没有更简洁的方法来完成这个任务?(可能使用Junit的@Rule注解?)


14
如果JUnit测试抛出除了预期异常以外的任何异常,就会判定测试失败。通常不应该出现任何异常。 - Raedwald
2
在JUnit中,失败和错误之间难道没有区别吗?第一个意味着测试失败,第二个意味着发生了意外情况。 - Victor Basso
2
可能是重复的问题:如何测试特定异常是否未被抛出? - Ciro Santilli OurBigBook.com
@Vituel 是的,在NetBeans中有区别,而且非常清晰。错误是红色的,失败是黄色的。 - Alonso del Arte
@Raedwald 在截止日期的压力下,我可能会这样看待它。但是如果有充足的时间,我不想在红色和绿色之间没有黄色。 - Alonso del Arte
20个回答

2
我最终是这样做的。
@Test
fun `Should not throw`() {
    whenever(authService.isAdmin()).thenReturn(true)

    assertDoesNotThrow {
        service.throwIfNotAllowed("client")
    }
}

1
这可能不是最佳方式,但它确保在被测试的代码块中不会抛出异常。
import org.assertj.core.api.Assertions;
import org.junit.Test;

public class AssertionExample {

    @Test
    public void testNoException(){
        assertNoException();
    }    

    private void assertException(){
        Assertions.assertThatThrownBy(this::doNotThrowException).isInstanceOf(Exception.class);
    }

    private void assertNoException(){
        Assertions.assertThatThrownBy(() -> assertException()).isInstanceOf(AssertionError.class);
    }

    private void doNotThrowException(){
        //This method will never throw exception
    }
}

1
使用 assertNull(...)
@Test
public void foo() {
    try {
        //execute code that you expect not to throw Exceptions.
    } catch (Exception e){
        assertNull(e);
    }
}

8
我认为这是有误导性的。catch块永远不会被执行,所以assertNull也不会被执行。然而,快速阅读者会产生一种印象,即一个断言被作出,真正验证了非抛出情况。换句话说:如果catch块被执行,则异常始终为非null - 因此可以用简单的fail替换它。 - Andreas
1
确实有些误导人,但是等等,哦我明白了,assertNull(e)会将测试报告为失败,因为在catch块中已经声明e不能为null...Mike这只是奇怪的编程 :-/ ...是的,至少像Andreas说的那样使用fail() - Julien
确实很奇怪!请忽略。 - Mike Rapadas

1

你可以基于junit的断言创建任何类型的自定义断言,因为它们专门设计用于创建用户定义的断言,旨在与junit的断言完全相同:

static void assertDoesNotThrow(Executable executable) {
    assertDoesNotThrow(executable, "must not throw");
}
static void assertDoesNotThrow(Executable executable, String message) {
    try {
        executable.execute();
    } catch (Throwable err) {
        fail(message);
    }
}

现在正在测试所谓的场景methodMustNotThrow,并以junit风格记录所有失败:

//test and log with default and custom messages
//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "custom facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "message");
//See implementation of methodMustNotThrow below

一般来说,在任何情况下,通过调用fail(someMessage)可以立即使测试失败,无论在何处都是有可能的,只要它有意义。这个方法就是为了这个目的而设计的。例如,在try/catch块中使用它,以便在测试用例中抛出任何异常时失败:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
try{methodMustNotThrow(1);}catch(Throwable e){Assertions.fail("must not throw");}

这是我们测试方法的示例,假设我们有这样一个方法,在特定情况下必须不会失败,但它可能会失败:

void methodMustNotThrow(int x) throws Exception {
    if (x == 1) return;
    throw new Exception();
}

上述方法是一个简单的示例。但这适用于复杂情况,其中故障不太明显。 以下是导入:

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import static org.junit.jupiter.api.Assertions.*;

有相对比较好的选项来检查断言是否被抛出,而不涉及创建自定义代码。@Rule是其中之一。 - Vargan
@Vargan 我已经指出了创建自己的断言方法的方式,这是由JUnit专门为创建自己的断言目的而设计的。JUnit通过设计提供了这个功能,特别是为了创建自己的规则,扩展JUnit的行为,使用尚未实现的断言。因为在这个世界上并不是所有的东西都被实现了。这些断言的工作方式与JUnit断言相同,无论是通过还是失败以及报告失败。 - armagedescu
@Vargan 只是好奇。你是否见过在junit 5中引入的assertThrows/assertDoesNotThrow?如果有比这些更好的选择,你如何解释它们在测试框架中的引入?你也可以尝试调试这些断言来查看实现。你会惊讶地发现它们的实现和用法与我的实现非常相似。 - undefined
我经常使用Junit 5,是的。但我不太喜欢重复造轮子。我知道实现不会相差太远。但从单元测试的角度来看,我可以重用它们,代码、处理程序、注解,所有这些都在幕后处理,而无需我重新编写这些代码。 - undefined
@Vargan 你还没有回答我的问题。所以,字面上引用有相当更好的选项来检查这个断言。我发现JUnit 5中的断言与我之前版本中的相当相似,而之前的版本中并没有这个断言。所以,你是在说JUnit 5重新发明了轮子,并引入了一个相当更糟的选项来检查这个断言吗?还要记住,我确切地使用了专门为此目的设计的JUnit功能,这个功能是为开发人员提供一种创建自己断言的方式。 - undefined

1
如果您想测试您的测试目标是否消耗了异常,只需将测试保留为(使用jMock2模拟协作者):
@Test
public void consumesAndLogsExceptions() throws Exception {

    context.checking(new Expectations() {
        {
            oneOf(collaborator).doSth();
            will(throwException(new NullPointerException()));
        }
    });

    target.doSth();
 }

如果您的目标确实消耗了抛出的异常,测试将通过,否则测试将失败。

如果您想测试异常消耗逻辑,则情况会变得更加复杂。我建议将消耗委托给一个可以模拟的协作者。因此测试可以是:

@Test
public void consumesAndLogsExceptions() throws Exception {
    Exception e = new NullPointerException();
    context.checking(new Expectations() {
        {
            allowing(collaborator).doSth();
            will(throwException(e));

            oneOf(consumer).consume(e);
        }
    });

    target.doSth();
 }

但有时候如果你只想记录日志,它可能过于设计复杂了。在这种情况下,如果您坚持进行TDD,则可以参考这篇文章(http://java.dzone.com/articles/monitoring-declarative-transac, http://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/)。


1

我遇到了同样的情况,我需要检查异常在应该抛出时是否被抛出,而且只有在应该抛出时才抛出。最终我利用异常处理程序编写了以下代码:

    try {
        functionThatMightThrowException()
    }catch (Exception e){
        Assert.fail("should not throw exception");
    }
    RestOfAssertions();

我的主要收益在于它非常直接,检查“当且仅当”的另一种方式在相同的结构中也非常容易。

欢迎来到SO。您的问题因为已经有17个回答且提问已经7年了而被标记为“晚回答”进行审核。虽然您的回答可能会提供一些价值,但是非常晚的回答通常会被投票降低评分。 - GoodJuJu
此外,它(几乎)与OP在最初提出的解决方案完全相同...寻求改进。 - Stephen C

0

0
你可以使用 @Rule 并调用 reportMissingExceptionWithMessage 方法来实现,如下所示: 这是 Scala 代码。

enter image description here


3
private val是什么编程语言?显然不是Java ;p请不要提供代码截图,这是不受欢迎的。 - Andremoniy
1
我看到你提到了这是Scala,但说在Java中可以“轻松完成”并不是一个有力的论据,很抱歉。 - Andremoniy
我已经删除了让你困扰的部分。我会尝试替换图片。但是我还没有想出如何添加代码。 - Crenguta S
ExpectedException.none()已被弃用。 - Rohit Gaikwad

0
您可以期望创建规则时不会抛出异常。
@Rule
public ExpectedException expectedException = ExpectedException.none();

ExpectedExceptions 用于断言抛出的异常。您提供的代码仅用于初始化规则,以便您可以添加对断言的要求。该代码本身并不添加任何价值。Javadoc 也说明了这一点:“/** * 返回一个 {@linkplain TestRule rule},它期望不会抛出任何异常(与没有此规则的行为相同)。 */” 因此,它将与没有它时具有完全相同的结果。 - BitfulByte
我同意你的观点,不会以那种方式使用它,但是可以断言没有抛出任何异常。如果测试通过,则应该足够说明未抛出异常,但另一方面,如果有疑问,则必须需要它。虽然很少,但有时候让它可见还是很好的。如果代码和情况发生了变化,我们没有针对某些特定边缘情况进行测试怎么办? - LazerBanana
我很好奇你会如何使用期望异常进行断言。如果需求发生变化,而你没有为特定的边缘情况编写测试,那么你就完了;-)一定要覆盖所有的边角情况。 - BitfulByte
你的意思是什么?你不是断言它,而是期望它。在这种情况下,你期望没有异常。不确定你在说什么。 - LazerBanana

-2

以下代码无法通过所有异常测试,包括已检查和未检查的异常:

@Test
public void testMyCode() {

    try {
        runMyTestCode();
    } catch (Throwable t) {
        throw new Error("fail!");
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接