Java 如何测试没有引发异常?
我知道这样做的一个方法是:Java 如何测试没有引发异常?,java,unit-testing,exception,junit,junit4,Java,Unit Testing,Exception,Junit,Junit4,我知道这样做的一个方法是: @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的@规则?)您的做法是错误的。只需测试您的
@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的
@规则
?)您的做法是错误的。只需测试您的功能:如果抛出异常,测试将自动失败。如果没有抛出异常,您的测试将全部显示为绿色
我注意到这个问题不时会引起人们的兴趣,所以我会稍微扩展一下
单元测试背景
当你做单元测试时,重要的是要给自己定义你认为是一个工作单位。基本上:对代码库的提取,可能包括也可能不包括表示单个功能的多个方法或类
或者,如第11页所定义:
单元测试是一段自动的代码,它调用被测试的工作单元,然后检查关于该单元的单个最终结果的一些假设。单元测试几乎总是使用单元测试框架编写的。它可以很容易地编写,运行速度也很快。它是可信的、可读的和可维护的。只要生产代码没有改变,结果是一致的
重要的是要认识到,一个工作单元通常不仅仅是一种方法,但在最基本的层面上,它是一种方法,然后由其他工作单元封装
理想情况下,您应该为每个单独的工作单元都有一个测试方法,这样您就可以随时查看哪里出了问题。在本例中,有一个名为getUserById()
的基本方法,它将返回一个用户,总共有3个工作单元
第一个工作单元应测试在输入有效和无效的情况下是否返回有效用户。数据源抛出的任何异常都必须在这里处理:如果没有用户,那么应该有一个测试来证明在找不到用户时抛出异常。这方面的一个示例可能是
IllegalArgumentException
,它是通过@Test(expected=IllegalArgumentException.class)
注释捕获的
一旦您处理了这个基本工作单元的所有用例,您就可以提高一个级别。在这里,您可以执行完全相同的操作,但只处理来自当前级别正下方的异常。这使您的测试代码保持良好的结构,并允许您快速运行整个体系结构以发现哪里出了问题,而不必到处乱跳
处理测试的有效和错误输入
现在应该清楚我们将如何处理这些异常。有两种类型的输入:有效输入和错误输入(输入在严格意义上是有效的,但不正确)
当您使用有效输入时,您正在设置隐式期望,即您编写的任何测试都将有效
这样的方法调用可以如下所示:existingUserById\u ShouldReturn\u UserObject
。如果此方法失败(例如:抛出异常),则您知道出现了问题,可以开始挖掘
通过添加另一个测试(nonExistingUserById\u ShouldThrow\u IllegalArgumentException
),该测试使用错误输入并期望出现异常,您可以看到您的方法是否对错误输入执行了它应该执行的操作
TL;博士
在测试中,您试图做两件事:检查有效输入和错误输入。通过将其分为两种方法,每种方法只做一件事,您将有更清晰的测试,并且可以更好地了解哪里出了问题
通过记住分层的工作单元,您还可以减少对层次结构中较高的层所需的测试量,因为您不必考虑较低层中可能出现的每一个错误:当前层下面的层实际上可以保证您的依赖关系正常工作,如果出现问题错误,它在当前层中(假设较低层本身不抛出任何错误)。如果您想测试测试目标是否使用异常。只需将测试保留为(使用jMock2的模拟协作者): 如果您的目标确实使用了抛出的异常,则测试将通过,否则测试将失败 如果你想测试你的异常消费逻辑,事情会变得更复杂。我建议将消费委托给可能被嘲笑的合作者。因此,测试可以是:
@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,本文(,)可能会有所帮助。如果您不幸捕获了代码中的所有错误。 你可以愚蠢地做
class DumpTest {
Exception ex;
@Test
public void testWhatEver() {
try {
thisShouldThrowError();
} catch (Exception e) {
ex = e;
}
assertEquals(null,ex);
}
}
Java8使这变得更加容易,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工具:aribitrary代码块和一个简单的注释
//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中通过
NumberFormatException
和路径完成的,如Double.parseDouble
。通过InvalidPathException
获取
假设您已经编写了一个验证Double.ParseDouble
的数字字符串的组件,可以使用正则表达式,也可以是手写解析器,或者是嵌入了一些其他域规则的东西,这些规则将Double的范围限制为特定的值
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()
}
@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));
}
@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!");
}
}
Assertions.assertThatCode(() -> toTest.method())
.doesNotThrowAnyException();
public class Printer {
public static void printLine(final String line) {
System.out.println(line);
}
}
@Test(expected = Test.None.class /* no exception expected */)
public void test_printLine() {
Printer.printLine("line");
}
@Rule
public ExpectedException expectedException = ExpectedException.none();
assertAll( () -> foo() )
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
}
}
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");
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.*;
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()
);
}
void testMeWell() throws SomeException {..}
assertDoesNotThrow(() -> {
testMeWell();
});
try {
functionThatMightThrowException()
}catch (Exception e){
Assert.fail("should not throw exception");
}
RestOfAssertions();