如何测试java-8自定义收集器的标识和关联约束

sli*_*lim 9 java java-stream

我已经为Java 8编写了一个自定义收集器.它的聚合器是一个包含一对列表的Map:

    @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;
        };
    }
Run Code Online (Sandbox Code Playgroud)

所以我认为它的组合是这样的:

    @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;
        };
    }
Run Code Online (Sandbox Code Playgroud)

我想测试收集器以确保它是否以及何时并行处理流,结果是正确的.

我怎样才能编写一个单元测试呢?

当然我可以写一个combiner直接调用的测试,但这不是我想要的.我希望证据在收集的背景下起作用.

Javadoc Collector说:

为确保顺序和并行执行产生相同的结果,收集器函数必须满足标识和关联约束.

我可以通过测试这些约束来获得对收集器的信心吗?怎么样?

sli*_*lim 3

感谢两位回答者,他们让我走上了我认为正确的道路。

当然可以创建一个并行流来执行Collector整体操作:

T result = myList.stream().parallel().collect(myCollector);
Run Code Online (Sandbox Code Playgroud)

但你不能保证它会分裂的边界,甚至不能保证它会分裂;缺少也许写一个自定义Spliterator

因此,测试合约似乎是一条可行的道路。相信Stream.collect()只要可行,就会做正确的事Collector。不测试“提供的”库是常见的做法。


JavaDocCollector定义了约束,甚至提供了描述关联性约束的代码。我们可以将此代码放入可在现实世界中使用的测试类中:

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));
    }

}
Run Code Online (Sandbox Code Playgroud)

剩下的就是用足够范围的输入来测试这一点。实现此目的的一种方法是使用 JUnit 4 的Theories运行程序。例如,要测试Collectors.joining()

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

(我觉得很高兴的是,我的测试代码不需要知道 的Collectors.joining()累加器的类型(API 未声明)即可使该测试正常工作)


请注意,这测试关联性和身份约束 - 您还需要测试收集器的域逻辑。collect()通过检查 a 的结果和直接调用的方法的平衡组合来实现这一点可能是最安全的Collector