我知道这样做的一种方法是:
@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
?)
你以错误的方式接近这个。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部变为绿色。
我注意到这个问题不时引起人们的兴趣,所以我会扩大一点。
单元测试的背景
当您进行单元测试时,向您自己定义您认为的工作单元非常重要。基本上:提取您的代码库,可能包含也可能不包含表示单个功能的多个方法或类。
或者,如第 11 页 The art of Unit Testing, 2nd Edition by Roy Osherove 中所定义:
单元测试是一段自动化的代码,它调用正在测试的工作单元,然后检查关于该单元的单个最终结果的一些假设。单元测试几乎总是使用单元测试框架编写的。它可以轻松编写并快速运行。它是可信赖的、可读的和可维护的。只要生产代码没有改变,它的结果是一致的。
重要的是要意识到一个工作单元通常不仅仅是一种方法,而是在最基本的层面上它是一种方法,然后它被其他工作单元封装。
https://i.stack.imgur.com/IPN6R.png
理想情况下,您应该对每个单独的工作单元都有一个测试方法,这样您就可以随时查看哪里出了问题。在此示例中,有一个名为 getUserById()
的基本方法,它将返回一个用户,总共有 3 个工作单元。
第一个工作单元应该测试在输入有效和无效的情况下是否返回了有效用户。
数据源抛出的任何异常都必须在这里处理:如果没有用户存在,则应该是一个测试,证明在找不到用户时会引发异常。这方面的一个示例可能是使用 @Test(expected = IllegalArgumentException.class)
注释捕获的 IllegalArgumentException
。
一旦你处理了这个基本工作单元的所有用例,你就提升了一个层次。在这里您所做的完全相同,但您只处理来自当前级别正下方的异常。这可以使您的测试代码保持良好的结构,并允许您快速运行架构以找到问题所在,而不必到处乱跳。
处理测试的有效和错误输入
此时应该清楚我们将如何处理这些异常。输入有两种类型:有效输入和错误输入(严格意义上的输入有效,但不正确)。
当您使用有效输入时,您正在设置隐含的期望,即您编写的任何测试都将起作用。
这样的方法调用可能如下所示:existingUserById_ShouldReturn_UserObject
。如果此方法失败(例如:抛出异常),那么您就知道出了点问题,您可以开始挖掘。
通过添加另一个使用 faulty 输入并期望出现异常的测试 (nonExistingUserById_ShouldThrow_IllegalArgumentException
),您可以查看您的方法是否按照错误输入完成了它应该做的事情。
TL;博士
您试图在测试中做两件事:检查有效和错误的输入。通过将其拆分为两种方法,每个方法都做一件事,您将获得更清晰的测试,并更好地了解哪里出了问题。
通过牢记分层的工作单元,您还可以减少层次结构中较高层所需的测试量,因为您不必考虑较低层中可能出错的所有事情:当前层之下的层是您的依赖项工作的虚拟保证,如果出现问题,它在您的当前层中(假设较低层本身不会引发任何错误)。
由于 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
是预期值的默认值。
如果您 import org.junit.Test.None
,则可以编写:
@Test(expected = None.class)
您可能会发现它更具可读性。
@Test
注释方法对解决 SonarQube 问题没有任何帮助。
assertDoesNotThrow(() -> Printer.printLine("line"));
JUnit 5(Jupiter)提供了三个函数来检查异常是否存在:
● assertAll()
all 提供的断言 executables
不会引发异常。
● assertDoesNotThrow()
断言执行
提供的 executable
/supplier
不会抛出任何类型的 exception。
此功能自 JUnit 5.2.0(2018 年 4 月 29 日)起可用
。
● assertThrows()
断言执行提供的 executable
抛出 expectedType
异常并返回 exception。
例子
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));
}
}
assertDoesNotThrow(myObject::myValidationFunction);
对于 5 之前的 JUnit 版本:
使用 AssertJ fluent assertions 3.7.0:
Assertions.assertThatCode(() -> toTest.method())
.doesNotThrowAnyException();
更新:
JUnit 5 引入了 assertDoesNotThrow()
断言,因此我更愿意使用它而不是向您的项目添加额外的依赖项。有关详细信息,请参阅 this answer。
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()
}
尽管有很多空间可以调整您想要如何表达这一点,但我一直是 fluent assertions 的粉丝。
关于
你以错误的方式接近这个。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部变为绿色。
这在原则上是正确的,但在结论上是错误的。
Java 允许控制流的异常。这是由 JRE 运行时本身在 API 中完成的,例如 Double.parseDouble
通过 NumberFormatException
和 Paths.get
通过 InvalidPathException
。
假设您已经编写了一个验证 Double.ParseDouble
的数字字符串的组件,可能使用正则表达式,可能是手写解析器,或者可能嵌入了一些其他域规则,将 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));
}
我还鼓励您在 input
上使用 Theories
或 Parameterized
参数化此测试,以便您可以更轻松地将此测试重新用于其他输入。或者,如果您想体验异国情调,您可以选择 test-generation tool(和 this)。 TestNG 对参数化测试有更好的支持。
我觉得特别令人不快的是使用 @Test(expectedException=IllegalArgumentException.class)
的建议,这个例外非常广泛。如果您的代码发生更改,使得被测组件的构造函数具有 if(constructorArgument <= 0) throw IllegalArgumentException()
,并且您的测试为该参数提供了 0,因为它很方便——这很常见,因为良好的生成测试数据是一个非常困难的问题——,那么您的测试将是绿条,即使它没有测试任何内容。这样的测试比没用还糟糕。
Assert.assertThrows
来检查某些代码是否引发了异常。
如果您不幸捕获代码中的所有错误。你可以愚蠢地做
class DumpTest {
Exception ex;
@Test
public void testWhatEver() {
try {
thisShouldThrowError();
} catch (Exception e) {
ex = e;
}
assertEquals(null,ex);
}
}
Exception ex
应该是 = null;
,然后才能对其进行测试。
Awaitility
的 untilAsserted(ThrowingRunnable assertion)
内断言。我调用的方法一开始总是会抛出异常,但我想断言它最终会停止这样做(根据 Awaitility 的参数)
虽然这篇文章现在已经有 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 的人有所帮助
Awaitility
的 untilAsserted(ThrowingRunnable assertion)
中执行此操作。被测系统当前正在我提供的 ThrowingRunnable 上引发特定异常,但我想给它一些时间,直到它停止这样做。但是,如果它会引发不同的异常,我希望测试立即失败。
使用 void 方法测试场景,例如
void testMeWell() throws SomeException {..}
不抛出异常:
Junit5
assertDoesNotThrow(() -> {
testMeWell();
});
如果您想测试您的测试目标是否使用异常。只需将测试保留为(使用 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/)可能会有所帮助。
@Test
public void foo() {
try {
//execute code that you expect not to throw Exceptions.
} catch (Exception e){
assertNull(e);
}
}
assertNull
。然而,快速阅读者会得到这样的印象,即做出了一个真正验证非抛出案例的断言。换句话说:如果到达 catch 块,异常总是非空的——因此它可以被一个简单的 fail
替换。
assertNull(e)
将报告测试失败,如所述 e
不能是 catch
块中的 null
。 .. Mike 这只是奇怪的编程:-/ ...是的,至少像 Andreas 说的那样使用 fail()
这可能不是最好的方法,但它绝对可以确保不会从正在测试的代码块中抛出异常。
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
}
}
我遇到了同样的情况,我需要检查是否应该抛出异常,并且只在应该抛出异常的时候。最终通过以下代码使用异常处理程序对我有利:
try {
functionThatMightThrowException()
}catch (Exception e){
Assert.fail("should not throw exception");
}
RestOfAssertions();
对我来说的主要好处是它非常简单,并且在相同的结构中检查“当且仅当”的另一种方式真的很容易
您可以期望通过创建规则不会引发异常。
@Rule
public ExpectedException expectedException = ExpectedException.none();
你可以通过使用@Rule 然后调用reportMissingExceptionWithMessage 方法来做到这一点,如下所示: 这是Scala 代码。
https://i.stack.imgur.com/Vchwf.png
private val
?这是什么语言?显然不是 Java ;p 请不要提供代码作为屏幕截图,不受欢迎。
偶然发现了这个问题,因为我创建了一些通用方法,例如
@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)
}
您可以基于来自 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);
}
}
现在测试所谓的场景方法MustNotThrow 并以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.*;
我最终会这样做
@Test
fun `Should not throw`() {
whenever(authService.isAdmin()).thenReturn(true)
assertDoesNotThrow {
service.throwIfNotAllowed("client")
}
}
以下所有异常的测试失败,检查或未检查:
@Test
public void testMyCode() {
try {
runMyTestCode();
} catch (Throwable t) {
throw new Error("fail!");
}
}
不定期副业成功案例分享
expected
注释。如果您想测试代码失败的场景并且想查看错误是否得到正确处理:使用expected
并可能使用断言来确定它是否已解决。throws IllegalArgumentException
添加到您的测试中。你最终想要的是,如果有异常,你的测试会变成红色。好吧,你猜怎么着?您不需要写fail()
。正如@Jeroen Vannevel 所写:“如果抛出异常,测试将自动失败。”