有时,当我们收到对jOOQ或其他库的拉取请求时,人们会将单元测试中的代码更改为更“惯用的JUnit”。 特别是,这意味着他们倾向于更改此代码(公认的不是那么漂亮的代码):
@Test
public void testValueOfIntInvalid() {try {ubyte((UByte.MIN_VALUE) - 1);fail();}catch (NumberFormatException e) {}try {ubyte((UByte.MAX_VALUE) + 1);fail();}catch (NumberFormatException e) {}
}
…成为“更好”和“更清洁”的版本:
@Test(expected = NumberFormatException.class)
public void testValueOfShortInvalidCase1() {ubyte((short) ((UByte.MIN_VALUE) - 1));
}@Test(expected = NumberFormatException.class)
public void testValueOfShortInvalidCase2() {ubyte((short) ((UByte.MAX_VALUE) + 1));
}
我们获得了什么?
没有!
当然,我们已经必须使用@Test
批注,因此我们不妨使用其expected
的属性对吗? 我声称这是完全错误的。 有两个原因。 当我说“两个”时,我的意思是“四个”:
1.在代码行数方面,我们并没有真正获得任何好处
比较语义上有趣的位:
// This:
try {ubyte((UByte.MIN_VALUE) - 1);fail("Reason for failing");
}
catch (NumberFormatException e) {}// Vs this:
@Test(expected = NumberFormatException.class)
public void reasonForFailing() {ubyte((short) ((UByte.MAX_VALUE) + 1));
}
给定或采用空格格式,基本语义信息量完全相同:
- 该方法正在测试中的
ubyte()
。 这不会改变 - 我们要传递给失败报告的消息(以字符串或方法名称表示)
- 异常类型和预期的事实
因此,即使从样式角度来看,这也不是真正有意义的更改。
2.我们还是必须将其重构
在注释驱动的方法中,我所能做的就是测试异常类型 。 例如,在以后要添加更多测试的情况下,我无法对异常消息做出任何假设。 考虑一下:
// This:
try {ubyte((UByte.MIN_VALUE) - 1);fail("Reason for failing");
}
catch (NumberFormatException e) {assertEquals("some message", e.getMessage());assertNull(e.getCause());...
}
3.单个方法调用不是单位
单元测试称为testValueOfIntInvalid()
。 因此, 通常在输入无效的情况下,要测试的语义“单位”是UByte
类型的valueOf()
行为的语义“单位”。 不适用于单个值,例如UByte.MIN_VALUE - 1
。
不应将其拆分为更小的单元,仅因为这是我们将@Test
注释塞入其功能范围的唯一方法。
TDD员工,请听此。 我从不希望将我的API设计或我的逻辑塞进“落后”测试框架(没有个人的,JUnit)所施加的一些怪异的限制中。 永不 ! “我的” API比“您的”测试重要100倍。 这包括我不想:
- 公开一切
- 使一切都没有定论
- 使一切都能注射
- 使所有内容均为非静态
- 使用注释。 我讨厌注解。
不。 你错了。 Java已经不是一种太复杂的语言,但是让我至少可以以我想要的任何方式使用它提供的一些功能。
不要因为测试而在我的代码上强加您的设计或语义上的毁损。
好。 我反应过度了。 我总是在存在批注的情况下 。 因为…
4.对于控制流结构而言,注释始终是错误的选择
一次又一次,我为Java生态系统中的注释滥用而感到惊讶。 注释对三件事有好处:
- 可处理的文档(例如
@Deprecated
) - 方法,成员,类型等的自定义“修饰符”(例如
@Override
) - 面向方面的编程(例如
@Transactional
)
并且要注意,@ @Transactional
是使其成为主流的少数几个真正有用的方面之一(日志挂钩是另一个方面,或者,如果绝对必须的话,依赖注入)。 在大多数情况下,AOP是解决问题的利基技术,您通常在普通程序中不希望这样做。
用注解对控制流结构进行建模绝对不是一个好主意,更不用说测试行为了
是。 Java已经采用了很长的(缓慢的)方法来包含更复杂的编程习惯用法。 但是,如果您对单元测试中偶尔的try { .. } catch { .. }
语句的冗长性感到不满,那么您可以找到解决方案。 是Java 8。
如何使用Java 8更好地做
JUnit lambda正在开发中: http : //junit.org/junit-lambda.html
他们向新的Assertions
类添加了新的功能API: https : //github.com/junit-team/junit-lambda/blob/master/junit5-api/src/main/java/org/junit/gen5/api /Assertions.java
一切都基于Executable
功能接口 :
@FunctionalInterface
public interface Executable {void execute() throws Exception;
}
该可执行文件现在可以用于实现断言引发(或不引发)异常的代码。 请参见Assertions
的以下方法
public static void assertThrows(Class<? extends Throwable> expected, Executable executable) {expectThrows(expected, executable);
}public static <T extends Throwable> T expectThrows(Class<T> expectedType, Executable executable) {try {executable.execute();}catch (Throwable actualException) {if (expectedType.isInstance(actualException)) {return (T) actualException;}else {String message = Assertions.format(expectedType.getName(), actualException.getClass().getName(),"unexpected exception type thrown;");throw new AssertionFailedError(message, actualException);}}throw new AssertionFailedError(String.format("Expected %s to be thrown, but nothing was thrown.", expectedType.getName()));
}
而已! 现在,那些反对try { .. } catch { .. }
块的冗长的人可以重写此代码:
try {ubyte((UByte.MIN_VALUE) - 1);fail("Reason for failing");
}
catch (NumberFormatException e) {}
…变成这样:
expectThrows(NumberFormatException.class, () -> ubyte((UByte.MIN_VALUE) - 1));
如果我想对异常进行进一步检查,可以这样做:
Exception e = expectThrows(NumberFormatException.class, () -> ubyte((UByte.MIN_VALUE) - 1));
assertEquals("abc", e.getMessage());
...
出色的工作,JUnit lambda团队!
函数式编程每次都会击败注释
注释被滥用了很多逻辑 ,主要是在JavaEE和Spring环境中,它们都迫切希望将XML配置移回Java代码。 这是错误的方法,这里提供的示例清楚地表明,与使用批注相比,几乎总是有一种更好的方法可以使用面向对象或功能编程来显式地写出控制流逻辑。
在@Test(expected = ...)
的情况下,我得出结论:
安息,
expected
(无论如何,它不再是JUnit 5 @Test
批注的一部分)
翻译自: https://www.javacodegeeks.com/2016/01/use-junits-expected-exceptions-sparingly.html