如何测试java-8自定义收集器的标识和关联性约束
我已经为Java8编写了一个自定义收集器。其聚合器是一个包含一对列表的映射:如何测试java-8自定义收集器的标识和关联性约束,java,java-stream,Java,Java Stream,我已经为Java8编写了一个自定义收集器。其聚合器是一个包含一对列表的映射: @Override public Supplier<Map<Boolean, List<Object>>> supplier() { return () -> { Map<Boolean, List<Object>> map = new HashMap<>(2);
@Override
public Supplier<Map<Boolean, List<Object>>> supplier() {
return () -> {
Map<Boolean, List<Object>> map = new HashMap<>(2);
map.put(false, new ArrayList<>());
map.put(true, new ArrayList<>());
return map;
};
}
@覆盖
公共供应商(){
返回()->{
Map Map=新的HashMap(2);
put(false,new ArrayList());
put(true,new ArrayList());
返回图;
};
}
所以我认为它的组合器是这样的:
@Override
public BinaryOperator<Map<Boolean, List<Object>>> combiner() {
return (a, b) -> {
a.get(false).addAll(b.get(false));
a.get(true).addAll(b.get(true));
return a;
};
}
@覆盖
公共二进制运算符组合器(){
返回(a,b)->{
a、 get(false).addAll(b.get(false));
a、 addAll(b.get(true));
返回a;
};
}
我想测试收集器,以确保当它并行处理流时,结果是正确的
我如何编写一个单元测试来测试这个问题
当然,我可以编写一个直接调用组合器的测试,但这不是我想要的。我想要证据证明它在收集的过程中起作用
收集器的Javadoc说明:
为了确保顺序执行和并行执行产生等效的结果,收集器函数必须满足标识和关联性约束
通过测试这些约束条件,我是否可以获得对收集器的信心?怎么做?你基本上是在问是否是关联的。因为标识是由(您使用的)每个标准集合保证遵循的
结合性
关联性是指以下内容:
在包含同一关联运算符行中两个或多个匹配项的表达式中,只要操作数序列不发生更改,执行操作的顺序就无关紧要。也就是说,重新排列此类表达式中的括号不会更改其值。考虑下列方程:
(2 + 3) + 4 = 2 + (3 + 4) = 9
2 × (3 × 4) = (2 × 3) × 4 = 24
--
是列表。addAll
s是关联的。
让我们用例子来说明:
import java.util.*;
public class Main {
// Give addAll an operator look.
static <T> List<T> myAddAll(List<T> left, List<T> right) {
List<T> result = new ArrayList<>(left);
result.addAll(right);
return result;
}
public static void main(String[] args) {
List<Integer> a = Arrays.asList(1, 2, 3);
List<Integer> b = Arrays.asList(4, 5, 6);
List<Integer> c = Arrays.asList(7, 8, 9);
// Combine a and b first, then combine the result with c.
System.out.println(myAddAll(myAddAll(a, b), c)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Combine b and c first, then combine a with the result.
System.out.println(myAddAll(a, myAddAll(b, c))); // [1, 2, 3, 4, 5, 6, 7, 8, 9]
}
}
import java.util.*;
公共班机{
//给addAll看一眼。
静态列表myAddAll(左列表、右列表){
列表结果=新建ArrayList(左);
结果:addAll(右);
返回结果;
}
公共静态void main(字符串[]args){
列表a=Arrays.asList(1,2,3);
listb=Arrays.asList(4,5,6);
listc=Arrays.asList(7,8,9);
//先把a和b结合起来,然后把结果和c结合起来。
System.out.println(myAddAll(myAddAll(a,b,c));//[1,2,3,4,5,6,7,8,9]
//先把b和c组合起来,然后把a和结果组合起来。
System.out.println(myAddAll(a,myAddAll(b,c));//[1,2,3,4,5,6,7,8,9]
}
}
测试
Collector
的契约正是您编写的:确保组合器同时具有identity和associativity属性。如果你遵循这一点,你就不会有任何问题(当然,如果需要的话,一定要暗示你的想法)
然后,测试归结为简单测试组合器是否具有这两个属性。标识部分由equals保证,关联部分由编写类似于上述代码的测试来处理。归根结底,这是因为,@mrmcgreg在评论中说,您不应该测试框架本身:这是Java作者的责任。如果在证明组合器满足这两个属性后遇到任何问题,您可能应该向Java提交一个bug。Olivier回答了关联性/标识部分
关于测试,您可以编写自己的测试用例,希望能够涵盖所有角落的测试用例,或者尝试基于属性的测试ala Haskell(例如Java)
这将生成一组随机对象,并查看在应用运算符时声明的属性是否适用于所有对象。要进入一个更陡峭的学习曲线,但之后值得付出努力:)感谢两位回答者,他们让我走上了我认为正确的道路
当然,也可以创建一个并行流,以将收集器作为一个整体来运行:
T result = myList.stream().parallel().collect(myCollector);
但你不能保证它会分裂的边界,甚至不能保证它会分裂;除了编写自定义的拆分器
因此,测试合同似乎是一条路要走。信任Stream.collect()
在采集器正常工作的情况下做正确的事情。通常不测试“提供的”库
Collector
JavaDoc定义了约束,甚至提供了描述关联性约束的代码。我们可以将此代码拉入一个在现实世界中可用的测试类:
public class CollectorTester<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final Function<A, R> finisher;
private final BinaryOperator<A> combiner;
public CollectorTester(Collector<T, A, R> collector) {
this.supplier = collector.supplier();
this.accumulator = collector.accumulator();
this.combiner = collector.combiner();
this.finisher = collector.finisher();
}
// Tests that an accumulator resulting from the inputs supplied
// meets the identity constraint
public void testIdentity(T... ts) {
A a = supplier.get();
Arrays.stream(ts).filter(t -> t != null).forEach(
t -> accumulator.accept(a, t)
);
assertThat(combiner.apply(a, supplier.get()), equalTo(a));
}
// Tests that the combiner meets the associativity constraint
// for the two inputs supplied
// (This is verbatim from the Collector JavaDoc)
// This test might be too strict for UNORDERED collectors
public void testAssociativity(T t1, T t2) {
A a1 = supplier.get();
accumulator.accept(a1, t1);
accumulator.accept(a1, t2);
R r1 = finisher.apply(a1); // result without splitting
A a2 = supplier.get();
accumulator.accept(a2, t1);
A a3 = supplier.get();
accumulator.accept(a3, t2);
R r2 = finisher.apply(combiner.apply(a2, a3)); // result with splitting
assertThat(r1, equalTo(r2));
}
}
(我感到高兴的是,我的测试代码不需要知道收集器的类型。joining()
的累加器(API未声明)才能运行此测试)
注意,这只测试关联性和标识约束——您还需要测试收集器的域逻辑。通过检查collect()
的结果和直接调用Collector
的方法来实现这一点可能是最安全的。实际上取决于流。由拆分器
决定如何对不同线程的数据进行分割。因此,如果您实现了一个拆分器
,该拆分器具有拆分数据的trySplit
,那么您可以并行地“强制”执行。但是请注意,几乎不可能通过测试来验证并行代码的正确功能。另一方面,您的收集器与@Boristespider有何不同?@Boristespider非常相似,但是输入类型是或,它被收集到一个包含列表和列表的类型中。不知道基本收集器是如何测试的。Java本身的测试代码在哪里?据我所知,累加器在哪里
@RunWith(Theories.class)
public class MaxCollectorTest {
private final Collector<CharSequence, ?, String> coll = Collectors.joining();
private final CollectorTester<CharSequence, ?, String> tester = new CollectorTester<>(coll);
@DataPoints
public static String[] datapoints() {
return new String[] { null, "A", "rose", "by", "any", "other", "name" };
}
@Theory
public void testAssociativity(String t1, String t2) {
assumeThat(t1, notNullValue());
assumeThat(t2, notNullValue());
tester.testAssociativity(t1, t2);
}
@Theory
public void testIdentity(String t1, String t2, String t3) {
tester.testIdentity(t1, t2, t2);
}
}