如何测试没有异常抛出?


238

我知道一种方法是:

@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?)


10
如果JUnit测试引发除预期异常之外的任何异常,则将判断该JUnit测试失败。通常没有例外。
Raedwald

JUnit中的失败和错误之间没有区别吗?第一个表示测试失败,第二个表示发生了意外情况。
Vituel 2014年

Answers:


198

您正在以错误的方式处理此问题。只需测试您的功能:如果引发异常,则测试将自动失败。如果没有异常抛出,您的测试将全部变为绿色。

我注意到这个问题会不时引起人们的兴趣,所以我会扩大一点。

单元测试的背景

在进行单元测试时,重要的是要自己定义要考虑的工作单元。基本上是:对代码库的提取,其中可能包含也可能不包含代表单个功能的多个方法或类。

或者,如Roy Osherove2版第11页的“单元测试的艺术”中所定义:

单元测试是一个自动化的一段代码调用工作单元被测试,然后检查有关单元的单个的最终结果的一些假设。单元测试几乎总是使用单元测试框架编写的。它可以轻松编写并快速运行。它是可信赖的,可读的和可维护的。只要生产代码未更改,其结果就是一致的。

重要的是要认识到,一个工作单元通常不仅是一种方法,而且在最基本的层次上是一种方法,然后再由其他工作单元封装。

在此处输入图片说明

理想情况下,您应该对每个单独的工作单元都有一种测试方法,以便始终可以立即查看出现问题的地方。在此示例中,有一个称为的基本方法getUserById(),它将返回一个用户,并且总共有3个工作单元。

第一个工作单元应测试在输入有效和无效的情况下是否返回了有效用户。
数据源引发的任何异常都必须在这里进行处理:如果没有用户,则应该进行测试以证明在找不到用户时引发了异常。注释IllegalArgumentException捕获的可能是其中的一个示例@Test(expected = IllegalArgumentException.class)

处理完此基本工作单元的所有用例后,就可以上一层楼。在这里,您做的完全一样,但是您只处理来自当前级别正下方的级别的异常。这样可以使您的测试代码保持良好的结构化,并允许您快速遍历整个体系结构以查找问题所在,而不必四处寻找。

处理测试的有效和错误输入

此时,应该清楚我们将如何处理这些异常。输入有2种类型:有效输入和错误输入(从严格意义上讲,输入是有效的,但不正确)。

当您使用有效输入时,您正在设置隐式期望,无论您编写什么测试都可以工作。

这样的方法调用如下所示:existingUserById_ShouldReturn_UserObject。如果此方法失败(例如:引发了异常),则说明出了点问题,可以开始挖掘。

通过添加另一个nonExistingUserById_ShouldThrow_IllegalArgumentException使用错误输入并期望异常的测试(),您可以查看您的方法是否对错误的输入执行了应做的工作。

TL; DR

您试图在测试中做两件事:检查输入是否有效。通过将其分成两种分别执行某项操作的方法,您将获得更清晰的测试,并更好地了解问题出在哪里。

通过牢记分层的工作单元,您还可以减少层次结构中较高的层所需的测试数量,因为您不必考虑在较低层可能出错的所有事情:当前层以下的层是对您的依赖项工作的虚拟保证,如果出现问题,则在当前层中(假设较低层本身不会引发任何错误)。


2
问题是我正在尝试TDD,而我使用的合作者之一抛出了异常。因此,我需要测试一个事实,即我正在使用协作者抛出的异常
Ankit Dhingra

6
您是说功能取决于对异常的处理吗?那是代码的味道:异常可以优雅地让您捕获问题。它们不用于流量控制。如果要测试应引发异常的方案,则应使用expected注释。如果要测试代码失败的情况,并且想查看错误是否得到正确处理,请使用expected或可能使用断言来确定是否已解决该错误。
Jeroen Vannevel

问题是我无法从协作者中发生的异常中恢复,我所做的只是使用log.debug(“ Error message”)记录问题。因此,没有任何副作用可以作为我可以断言的catch块的一部分。
Ankit Dhingra

5
@JeroenVannevel测试是否正确处理了导致引发异常的错误情况,这是完全有效的。
托尔比约恩Ravn的安徒生

1
@dpk是的,可以。您将添加throws IllegalArgumentException到测试中。最后,您想要的是如果有异常,测试会变成红色。好吧,你猜怎么着?您不需要写fail()。正如@Jeroen Vannevel所写:“如果引发异常,则测试将自动失败。”
Amedee Van Gasse

132

由于SonarQube的规则“ squid:S2699”,我偶然发现了这一点:“在此测试用例中至少添加一个断言。”

我进行了一个简单的测试,唯一的目标是要在不引发异常的情况下进行测试。

考虑以下简单代码:

public class Printer {

    public static void printLine(final String line) {
        System.out.println(line);
    }
}

可以添加哪种断言来测试此方法?当然,您可以尝试解决它,但这仅是代码膨胀。

该解决方案来自JUnit本身。

如果没有抛出异常,并且您想显式地说明这种行为,只需expected在以下示例中添加:

@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
    Printer.printLine("line");
}

Test.None.class 是期望值的默认值。


30
我认为这是最好的答案。公认的答案很好,作者正确指出了代码的味道。但是他并没有真正回答具体问题。
HellishHeat

4
有趣的是,期望的默认值是None,因此只需使用@Test注释方法即可。
oziomajnr


41

JUnit 5(Jupiter)提供了三个功能来检查异常是否存在:

assertAll​()

断言所有供应executables
  并不会抛出异常。

assertDoesNotThrow​()

断言
  提供的executable/的supplier
执行不会引发任何异常

  此功能
  自JUnit 5.2.0(2018年4月29日)起可用。

assertThrows​()

断言提供的执行将executable
引发的异常expectedType
  并返回该异常

package test.mycompany.myapp.mymodule;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class MyClassTest {

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw() {
        String myString = "this string has been constructed";
        assertAll(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_has_been_constructed_then_myFunction_does_not_throw__junit_v520() {
        String myString = "this string has been constructed";
        assertDoesNotThrow(() -> MyClass.myFunction(myString));
    }

    @Test
    void when_string_is_null_then_myFunction_throws_IllegalArgumentException() {
        String myString = null;
        assertThrows(
            IllegalArgumentException.class,
            () -> MyClass.myFunction(myString));
    }

}

1
这是最好的答案。其他答案正在讨论旧版本的JUnit
Tejesh Raut

29

Java 8使这变得容易得多,而Kotlin / Scala则加倍了。

我们可以写一些实用程序类

class MyAssertions{
  public static void assertDoesNotThrow(FailingRunnable action){
    try{
      action.run()
    }
    catch(Exception ex){
      throw new Error("expected action not to throw, but it did!", ex)
    }
  }
}

@FunctionalInterface interface FailingRunnable { void run() throws Exception }

然后您的代码将变得简单:

@Test
public void foo(){
  MyAssertions.assertDoesNotThrow(() -> {
    //execute code that you expect not to throw Exceptions.
  }
}

如果您没有访问Java-8的权限,那么我将使用一个痛苦的旧Java工具:任意代码块和简单注释

//setup
Component component = new Component();

//act
configure(component);

//assert 
/*assert does not throw*/{
  component.doSomething();
}

最后,使用kotlin,我最近爱上了一种语言:

fun (() -> Any?).shouldNotThrow() 
    = try { invoke() } catch (ex : Exception){ throw Error("expected not to throw!", ex) }

@Test fun `when foo happens should not throw`(){

  //...

  { /*code that shouldn't throw*/ }.shouldNotThrow()
}

尽管仍有很多空间可以准确表达您的表达方式,但我一直是流利断言的拥护者


关于

您正在以错误的方式处理此问题。只需测试您的功能:如果引发异常,则测试将自动失败。如果没有异常抛出,您的测试将全部变为绿色。

这原则上是正确的,但结论上是错误的。

Java允许控制流异常。这是由API中的JRE运行时本身Double.parseDouble通过a NumberFormatExceptionPaths.get通过a完成的InvalidPathException

假设您已经编写了一个用于验证Number字符串的组件Double.ParseDouble,或者使用正则表达式,手写解析器,或者嵌入一些将某些双精度域的范围限制为特定范围的其他域规则的方法,那么如何最好地对此进行测试零件?我认为一个显而易见的测试是断言,当解析结果字符串时,不会引发任何异常。我将使用上述assertDoesNotThrow/*comment*/{code}块编写该测试。就像是

@Test public void given_validator_accepts_string_result_should_be_interpretable_by_doubleParseDouble(){
  //setup
  String input = "12.34E+26" //a string double with domain significance

  //act
  boolean isValid = component.validate(input)

  //assert -- using the library 'assertJ', my personal favourite 
  assertThat(isValid).describedAs(input + " was considered valid by component").isTrue();
  assertDoesNotThrow(() -> Double.parseDouble(input));
}

我也鼓励您input使用Theories或参数化此测试,Parameterized以便您可以更轻松地将该测试用于其他输入。或者,如果你想要去的异国风情,你可以去一个测试生成工具(与)。TestNG对参数化测试有更好的支持。

我发现特别令人讨厌的是使用的建议@Test(expectedException=IllegalArgumentException.class)这种例外情况危险地广泛。如果您的代码发生了更改,使得被测组件的构造函数具有if(constructorArgument <= 0) throw IllegalArgumentException(),并且您的测试为该参数提供了0,因为它很方便-这是很常见的,因为生成良好的测试数据是一个非常困难的问题-那么您的测试即使未进行任何测试,也会显示为绿色。这样的测试比没有用要糟糕。


2
(关于预期异常的使用)从JUnit 4.13开始,您可以Assert.assertThrows用来检查某些代码是否抛出异常。
MageWind

22

如果您很不幸地无法捕获代码中的所有错误。你可以愚蠢地做

class DumpTest {
    Exception ex;
    @Test
    public void testWhatEver() {
        try {
            thisShouldThrowError();
        } catch (Exception e) {
            ex = e;
        }
        assertEquals(null,ex);
    }
}

1
只是一个小建议,Exception ex应该可以= null;进行测试。
Denees

4
这不是一个很好的解决方案。如果该方法不应该引发异常,则将引发有用的错误消息。只需调用不应引发异常的方法,然后测试其返回值(或副作用,如记录异常)即可。如果以后意外引发异常,则测试将失败。
NamshubWriter 2015年

3
或只是将Assert.fail()放到更容易,更漂亮的IMO中。
isaac.hazan

是的,我同意你的看法。另一种方法是在方法@Test上添加注释(预期= InvalidRequestException.class)
Ben Tennyson

您的拼写令人困惑:thisShouldThroughError- > thisShouldThrowError
奥斯卡·布拉沃


7

尽管此职位现在已有6年历史,但Junit世界已经发生了很多变化。借助Junit5,您现在可以使用

org.junit.jupiter.api.Assertions.assertDoesNotThrow()

例如:

public void thisMethodDoesNotThrowException(){
   System.out.println("Hello There");
}

@Test
public void test_thisMethodDoesNotThrowException(){
  org.junit.jupiter.api.Assertions.assertDoesNotThrow(
      ()-> thisMethodDoesNotThrowException()
    );
}

希望它对使用较新版本Junit5的人们有所帮助


我希望这里有一种方法可以指定具体的异常类。我必须做这里面AwaitilityuntilAsserted(ThrowingRunnable assertion)。被测系统当前在我提供的ThrowingRunnable上抛出特定异常,但我想给它一些时间,直到它停止这样做为止。但是,如果它将引发其他异常,我希望测试立即失败。
乌贝戈什

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

但是有时候,如果您只想记录它,它的设计可能会过分设计。在这种情况下,这篇文章(http://java.dzone.com/articles/monitoring-declarative-transachttp://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there -an-easy-way /)如果在这种情况下坚持tdd可能会有所帮助。


1

使用assertNull(...)

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

6
我会说这是误导。catch块永远不会到达,因此assertNull也不会执行。但是,快速读者会给人以这样的印象,即可以断言确实验证了非抛出情况。换句话说:如果到达catch块,则异常始终为非null-因此可以将其替换为simple fail
安德里亚斯

确实有误导性,.....但是,等等,...哦,我知道... assertNull(e)将报告测试失败,如所述e不能null在代码catch块中... Mike,这只是奇怪的编程:-/。 ..是的,至少fail()像Andreas所说的那样使用
朱利安(Julien)

1

您可以期望通过创建规则不会引发异常。

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

ExpectedExceptions用于声明抛出的异常。您提供的代码仅用于初始化规则,因此您可以添加断言的要求。这段代码本身根本不增加任何值。javadoc还声明了这一点:“ / ** *返回一个{@linkplain TestRule规则},该规则不希望引发任何异常*(与没有该规则的行为相同)。 。
Pim Hazebroek '17

我同意您的看法,不会以这种方式使用它,但是可以断言没有引发任何异常。如果测试通过,则应该足以说明尚未引发异常,但另一方面,如果有问题,则必须对此进行处理。很少但有时仍然是可见的是很好的。如果代码和环境发生了变化,而我们又没有对某些特殊情况进行测试,该怎么办?
LazerBanana

我很好奇,看看您将如何使用预期的例外来断言。是的,如果需求发生变化,并且您没有针对特定的边缘盒进行测试,那么您会被拧紧;-)始终覆盖所有角盒。
Pim Hazebroek

你什么意思?您不会对此断言,您会期望的。在这种情况下,您预计不会例外。不知道你在做什么。
LazerBanana '17

0

这可能不是最好的方法,但是它可以确保不会从正在测试的代码块中引发异常。

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

0

您可以使用@Rule,然后调用方法reportMissingExceptionWithMessage来完成此操作,如下所示:这是Scala代码。

在此处输入图片说明


1
private val?这是什么语言?显然不是Java; p并且请不要提供代码作为屏幕截图,因此不受欢迎。
Andremoniy

我看到您已经提到它是Scala,但是说它可以用Java 轻松完成并不是一个有力的论据,对不起
Andremoniy 19''7 -29-9

我删除了困扰您的部分。我也会尝试替换图像。还没有想出如何添加的代码,但..
Crenguta小号

-1

以下内容未通过所有异常(选中或未选中)的测试:

@Test
public void testMyCode() {

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

-1

您可以基于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);
    }
}

并测试:

//the following will succeed
assertDoesNotThrow(()->methodMustNotThrow(1));
assertDoesNotThrow(()->methodMustNotThrow(1), "fail with specific message: facepalm");
//the following will fail
assertDoesNotThrow(()->methodMustNotThrow(2));
assertDoesNotThrow(()-> {throw new Exception("Hello world");}, "Fail: must not trow");

一般而言,在任何情况下,只要有意义,任何地方都可能会立即使测试失败(“ bla bla bla”)。例如,如果在测试用例中抛出任何内容,则在try / catch块中使用它会失败:

try{methodMustNotThrow(1);}catch(Throwable e){fail("must not throw");}
//or
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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.