如何测试没有抛出异常?

我知道有一种方法是:

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

463718 次浏览

你想错了。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部显示为绿色。

我注意到这个问题有时会引起人们的兴趣,所以我将展开一点。

单元测试的背景

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

或者,如第11页《单元测试的艺术》第二版,作者Roy Osherove中定义的:

单元测试是一段自动的代码,它调用被测试的工作单元,然后检查关于该单元的单个最终结果的一些假设。单元测试几乎总是使用单元测试框架编写的。它易于编写,运行迅速。它是可靠的、可读的和可维护的。只要生产代码没有改变,它的结果是一致的。

重要的是要意识到一个工作单位通常不只是一个方法,但在非常基本的层面上,它是一个方法,之后它被其他工作单元封装。

enter image description here

理想情况下,您应该为每个单独的工作单元都有一个测试方法,这样您就可以立即查看哪里出了问题。在这个例子中,有一个名为getUserById()的基本方法,它将返回一个用户,总共有3个单元的工作。

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

一旦您处理了这个基本工作单元的所有用例,您就可以提升一个级别。这里的操作与此完全相同,但是只处理来自当前级别下一层的异常。这使您的测试代码保持良好的结构,并允许您快速运行整个体系结构以找到问题所在,而不必到处跳来跳去。

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

现在应该很清楚如何处理这些异常了。有两种类型的输入:有效的输入和错误的输入(严格意义上的输入是有效的,但它不是正确的)。

当你使用有效的输入时,你是在设置隐式期望,无论你写什么测试,都将工作。

这样的方法调用可以像这样:existingUserById_ShouldReturn_UserObject。如果这个方法失败了(例如:抛出一个异常),那么你就知道出错了,可以开始挖掘了。

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

博士TL;

您试图在测试中做两件事:检查有效输入和错误输入。通过将它分成两个方法,每个方法做一件事,您将有更清晰的测试,并更好地了解哪里出了问题。

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

如果您想测试您的测试目标是否使用异常。只需要将测试保留为(使用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-transachttp://blog.novoj.net/2008/09/20/testing-aspect-pointcuts-is-there-an-easy-way/)可能会有所帮助。

如果你不幸捕获了代码中的所有错误。 你可以愚蠢地执行

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

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允许控制流的异常。这是由JRE运行时本身在api中完成的,比如Double.parseDouble通过NumberFormatExceptionPaths.get通过InvalidPathException

假设你已经编写了一个为Double.ParseDouble验证Number字符串的组件,可能使用了一个正则表达式,可能是一个手写的解析器,或者可能嵌入了一些其他域规则,将double的范围限制为特定的内容,如何最好地测试这个组件?我认为一个明显的测试是断言,当解析结果字符串时,不会抛出异常。我将使用上面的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));
}

我还鼓励你使用TheoriesParameterizedinput上参数化这个测试,这样你就可以更容易地将这个测试用于其他输入。或者,如果你想要异国情调,你可以选择测试生成工具(和)。TestNG对参数化测试有更好的支持。

我觉得特别讨厌的是建议使用@Test(expectedException=IllegalArgumentException.class)这种例外是危险的宽泛。如果你的代码改变了,测试的构造函数下的组件有if(constructorArgument <= 0) throw IllegalArgumentException(),并且你的测试为这个参数提供了0,因为它很方便——这很常见,因为良好地生成测试数据是一个非常困难的问题——那么你的测试将是绿条,即使它什么都没有测试。这样的测试比无用更糟糕。

使用assertNull(…)

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

以下是所有检查或未检查的异常都无法通过测试:

@Test
public void testMyCode() {


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

对于5之前的JUnit版本:

AssertJ流畅断言3.7.0:

Assertions.assertThatCode(() -> toTest.method())
.doesNotThrowAnyException();

更新:

JUnit 5引入了assertDoesNotThrow()断言,所以我更喜欢使用它,而不是向你的项目添加额外的依赖项。详见这个答案

我偶然发现这一点是因为SonarQube的规则“squid: s2699”:“向这个测试用例添加至少一个断言”。

我有一个简单的测试,它的唯一目标是不抛出异常。

考虑这段简单的代码:

public class Printer {


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

可以添加什么样的断言来测试这个方法? 当然,你可以在它周围做一个try-catch,但那只是代码膨胀

解决方案来自JUnit本身。

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

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

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

如果你import org.junit.Test.None,你可以这样写:

@Test(expected = None.class)

你可能会觉得更有可读性。

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

@Rule
public ExpectedException expectedException = ExpectedException.none();
你可以使用@Rule,然后调用方法reportMissingExceptionWithMessage,如下所示: 这是Scala代码

enter image description here

JUnit5为此添加了assertAll()方法。

assertAll( () -> foo() )

来源:JUnit 5 API

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


}

这可能不是最好的方法,但它肯定能确保不会从正在测试的代码块抛出异常。

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

你可以基于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)都有可能立即使测试失败,而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.*;

虽然这篇文章已经发布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新版本的人

用void方法来测试场景

void testMeWell() throws SomeException {..}

不扔是一个异常:

Junit5

assertDoesNotThrow(() -> {
testMeWell();
});

我也遇到了同样的情况,我需要检查异常是否在应该抛出时抛出,并且仅在应该抛出时抛出。 最终使用异常处理程序,以我的利益与以下代码:

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

对我来说,主要的好处是它非常直截了当,可以检查“如果且仅如果”的另一种方式;在同样的结构中真的很简单吗

在这个问题上遇到了麻烦,因为我创建了一些泛型方法

@Test
void testSomething() {
checkGeneric(anComplexObect)
}

https://newbedev.com/sonarqube-issue-add-at-least-one-assertion-to-this-test-case-for-unit-test-with-assertions中提出了一些注释的东西。

解决方案要简单得多。这就足够重命名&;checkgeneric &;方法来“assertgeneric”。

@Test
void testSomething() {
assertGeneric(anComplexObect)
}

我最后是这样做的

@Test
fun `Should not throw`() {
whenever(authService.isAdmin()).thenReturn(true)


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

AssertJ可以处理这种情况:

assertThatNoException().isThrownBy(() -> System.out.println("OK"));

查看文档获取更多信息https://assertj.github.io/doc/#assertj-core-exception-assertions-no-exception