【发布时间】:2020-05-26 22:17:51
【问题描述】:
我试图创建一个单元测试,以确保 list(或更一般地说,container)包含某些强制性项目,同时允许它也包含一些额外的可选项目(但同样来自预定义的选项列表)。
为了明确起见,我们假设列表:
-
必须包含项目
foo和bar; -
可能包含项目
optional; - 不得包含任何其他项目。
在 Java 中,使用来自 AssertJ 库的方便的 satisfiesAnyOf() 函数,此测试可以编写如下:
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class C {
@Test
public void t0() {
doTest(asList("foo", "bar"));
}
@Test
public void t1() {
doTest(asList("foo", "bar", "optional"));
}
private static void doTest(final List<String> items) {
assertThat(items).as("items").satisfiesAnyOf(
it -> assertThat(it).as("XXX").containsExactlyInAnyOrder("foo", "bar"),
it -> assertThat(it).as("YYY").containsExactlyInAnyOrder("foo", "bar", "optional"));
}
}
此代码的问题在于它只能使用 Java 8 编译器进行编译,而 Java 9+ 编译器会通过以下方式拒绝它:
C.java:[26,63] no suitable method found for containsExactlyInAnyOrder(java.lang.String,java.lang.String)
method org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrder(capture#1 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#1 of ? extends java.lang.String)
method org.assertj.core.api.ListAssert.containsExactlyInAnyOrder(capture#1 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#1 of ? extends java.lang.String)
C.java:[27,63] no suitable method found for containsExactlyInAnyOrder(java.lang.String,java.lang.String,java.lang.String)
method org.assertj.core.api.AbstractIterableAssert.containsExactlyInAnyOrder(capture#2 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#2 of ? extends java.lang.String)
method org.assertj.core.api.ListAssert.containsExactlyInAnyOrder(capture#2 of ? extends java.lang.String...) is not applicable
(varargs mismatch; java.lang.String cannot be converted to capture#2 of ? extends java.lang.String)
通过将每个 lambda 中的 it 从 List<? extends String> 显式转换为 List<String> 可以轻松修复客户端代码,这甚至不会更改生成的字节码:
assertThat(items).as("items").satisfiesAnyOf(
it -> assertThat((List<String>) it).as("XXX").containsExactlyInAnyOrder("foo", "bar"),
it -> assertThat((List<String>) it).as("YYY").containsExactlyInAnyOrder("foo", "bar", "optional"));
查看 AssertJ API,我无法弄清楚如何在没有不安全强制转换的情况下解决上述问题。
支持 Kotlin,等效的 Kotlin 代码编译得很好(it 的类型可以指定为 MutableList<out String> 或 List<String>) :
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class K {
@Test
fun t() {
val items = listOf("foo", "bar", "optional")
assertThat(items).`as`("items").satisfiesAnyOf({ assertThat(it).`as`("XXX").containsExactlyInAnyOrder("foo", "bar") },
{ assertThat(it).`as`("YYY").containsExactlyInAnyOrder("foo", "bar", "optional") })
}
}
问题:
- Java 8 和 Java 9 之间的 JLS 到底发生了什么变化?您能否指出规范中禁止成功编译的确切部分?
- 如何重写上述 Java 代码以避免显式类型转换?
【问题讨论】:
-
注意:最好使用
(List<String> it) -> assertThat(...,而不是强制转换。 -
@AndyTurner 确实,但这不会编译。
satisfiesAnyOf()期望的 lambda 类型应该是Consumer<List<? extends String>>。
标签: java generics kotlin crtp assertj