【问题标题】:Should I always use Lambda Expressions for Exception Tests?我应该始终使用 Lambda 表达式进行异常测试吗?
【发布时间】:2016-04-27 10:51:36
【问题描述】:

我总是用注释测试我的异常。

@Test (expected = Exception.class)
public void test1() {
  methodToTest() // throws an exception
}

我终于切换到 Java 8,并且遇到了 lambda 表达式。现在有另一个选项可以得到想要的结果。

@Test
public void test2() {
  assertThrows(Exception.class, () -> methodToTest());
}

public static <X extends Throwable> Throwable assertThrows(
  final Class<X> exceptionClass, final Runnable block) {
    try {
      block.run();
    } catch(Throwable ex) {
      if (exceptionClass.isInstance(ex))
        return ex;
    }
    fail("Failed to throw expected exception");
    return null;
  }

我知道,使用第二个版本,您可以更精确地检查单个方法,并且您不必担心单个测试中的其他方法也会引发预期的异常。此外,使用“assertThrows”方法,所有测试都可以具有相同的结构,因为这一切都归结为对断言的调用。

除了这两点之外,对新方法有什么赞成的意见吗?对我来说,只要我只测试一个方法,使用注释感觉还是更好的选择在一次测试中。

【问题讨论】:

  • 这个问题的 lambda 表达式在哪里?
  • "test2" 将 lambda 表达式交给 assertThrows 方法,该方法作为 Runnable 处理。
  • 你在问为什么必须使用 lambda 表达式?或者为什么你会做所有这些而不是使用@Test (expected = Exception.class) 注释?如果是后者,您几乎自己回答了,这是 2 点。
  • 抱歉我读得太快了

标签: java exception junit lambda java-8


【解决方案1】:

你错过了第三种方式,ExpectedException jUnit 规则:

public class SimpleExpectedExceptionTest {
   @Rule
   public ExpectedException thrown= ExpectedException.none();

   @Test
   public void myMethod_throws_no_exception_when_passed_greeting() {
       fixture.myMethod("hello");
   }

   @Test
   public void myMethod_throws_MyException_when_passed_farewell() {
       thrown.expect(MyException.class);
       fixture.myMethod("goodbye");
   }
}

我发现这比@Test (expected = ...) 版本更清晰,因为期望更接近方法调用。

还有普通的旧 Java 版本,我们过去常常使用它:

try {
   fixture.myMethod("should throw");
   fail("Expected an exception");
} catch (MyException e) {
   // expected  
}

其中哪个“更好”完全取决于上下文。不要普遍采用一种。选择在给定情况下为您提供最清晰测试的那个。

当您开始以 lambda 为中心的风格编写非测试代码时,您可能会发现自己想要使用以 lambda 为中心的assertThrows()

【讨论】:

  • 有了这个方法,能不能在调用抛出异常的方法后做事?
  • 使用第一种技术,您可以将任何您喜欢的代码放在 throwing 方法中,但是当方法按预期抛出时,它不会被执行。使用普通的旧 Java 版本,您当然可以在 catch 块中放置任何您喜欢的东西,比如关于异常的断言,或者在 catch 块之后,比如关于其他事物状态的断言。
  • 我是这么想的,那它和annotation相比带来了什么?它更冗长并且做同样的事情。不是批评,只是询问。
  • 对我来说,(expected = ...) 的断言出现在一个意想不到的地方——在测试之前。您也可以在ExpectedException 中创建更详细的期望,例如thrown.expectCause(...)
【解决方案2】:

如果您只想测试是否抛出异常,则第一种语法更好。它是标准的,简洁的,它可以防止你一遍又一遍地编写同样丑陋的 try-catch。

你可以有一个稍微复杂的测试,你想要断言抛出异常并且某些方法没有被调用。在这种情况下,手动捕获异常是合理的。

@Test
public void test1() {
   DBClient dbClient = spy(new DBClient());
   try {
       new Server().doRequest(new InvalidRequest(), dbClient); 
       fail("should have thrown");
   } catch (InvalidRequestException e) {
       verify(dbClient, times(0)).query(any(Query.class));
   }
}

关于在 lambdas 上更具体的使用,这取决于您。请注意,Runnable 不能抛出已检查的异常,因此您需要类似

@FunctionalInterface
public interface ThrowingRunnable<E extends Exception> {
     void run() throws E;
} 

【讨论】:

  • 旁注:使用布尔值hasThrown 和另一个断言是疯狂的。只需输入fail("should have thrown") after your new Server(...)` 行即可。
【解决方案3】:

我认为这两种方法都没有问题——风格问题没有对错之分;只需使用最适合每种情况的那个。我建议 assertThrows 还应该检查不属于预期类型的​​抛出异常,并且正如@Dici 建议的那样,使用允许检查异常的功能接口:

public static <X extends Throwable> Throwable assertThrows(
        final Class<X> exceptionClass, final CheckedRunnable block) {
    try {
        block.run();
    } catch(Throwable ex) {
        if (exceptionClass.isInstance(ex)) {
            return ex;
        } else {
            throw new AssertionError("Unexpected exception was thrown", ex);
        }
    }
    fail("Failed to throw expected exception");
}

@FunctionalInterface
public interface CheckedRunnable<R extends RuntimeException> {
    void run () throws Exception;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多