04-20.eri-test 测试Kafka Streams应用程序

Kafka Streams系列中的先前博客文章涵盖了无状态有状态的DSL API中的操作。 在此博客中,我们将探索一些示例,以演示如何使用测试实用程序基于Kafka Streams DSL API验证拓扑。

Kafka Streams提供了测试实用程序,可以为您的流处理管道执行单元测试,而不必依赖外部或嵌入式Kafka集群。 除了测试之外,这些实用程序还可以作为学习各种API功能的绝佳学习工具。

让我们从测试相关API的高级概述开始

Code is available on GitHub和tests can be executed by cloning the repo其次是mvn test

Key concepts

Initially, there were a few classes in the org.apache.kafka.streams.test package. 现在不推荐使用以下类

TestInputTopic

的实例TestInputTopic代表输入主题,您可以使用来向其中发送记录pipeInput方法(及其重载版本)。 创建TestInputTopic实例使用拓扑测试驱动程序(如下所述),并在需要时使用自定义序列化程序。 然后,您可以发送键值对,一次只能发送一个值,也可以批量发送(使用一个清单

TestOutputTopic

TestOutputTopic是发送-接收方程式的另一半,并补充了TestInputTopic。 您可以使用它来读取拓扑操作写入的输出主题中的记录。 它的方法包括读取记录(键值对),仅读取值,查询大小(尚未使用的当前记录数)等。

拓扑测试驱动程序

拓扑结构TestDriver包含对拓扑结构以及与您的Kafka Streams应用程序相关的配置。 如前所述,它用于创建以下对象的实例TestInputTopic,TestOutputTopic,提供对州立商店等的访问权限

High level flow

如果您正在使用马文,您可以将测试实用程序作为依赖项包含在内

        <dependency>
            <groupId>org.apache.kafka</groupId>
            <artifactId>kafka-streams-test-utils</artifactId>
            <version>2.4.0</version>
            <scope>test</scope>
        </dependency>

并且您将(最有可能)使用JUnit的的Hamcrest编写匹配规则...

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest-core</artifactId>
            <version>1.3</version>
            <scope>test</scope>
        </dependency>

这是一个测试用例的外观(类似于您用以下方法对任何Java代码进行单元测试的方式)JUnit等等。)

  • 使用以下命令设置全局状态(如果有)@课前注释方法
  • 使用的每个测试运行的设置状态@之前带注释的方法-通常在此处创建拓扑测试驱动程序等等
  • @测试验证方法拓扑结构
  • @后(和/或@下课以后)拆除任何状态(全局状态或其他状态)的方法

请确保您致电拓扑结构TestDriver.close()清理拓扑中的处理器及其关联状态。 否则可能会由于状态不一致而导致测试失败

现在您已经了解了概念和基本设置,下面让我们看一些具体示例。 我们将从无状态操作开始

Testing stateless操作s

filter

这里是拓扑结构使用过滤器方法仅允许长度大于5的值。

        StreamsBuilder builder = new StreamsBuilder();
        KStream<String,String> stream = builder.stream(INPUT_TOPIC);

        stream.filter((k, v) -> v.length() > 5).to(OUTPUT_TOPIC);

这是相应的测试:

    @Test
    public void shouldIncludeValueWithLengthGreaterThanFive() {

        topology = App.retainWordsLongerThan5Letters();
        td = new 拓扑测试驱动程序(topology, config);

        inputTopic = td.createInputTopic(App.INPUT_TOPIC, Serdes.String().serializer(), Serdes.String().serializer());
        outputTopic = td.createOutputTopic(App.OUTPUT_TOPIC, Serdes.String().deserializer(), Serdes.String().deserializer());

        assertThat(outputTopic.isEmpty(),is(true));

        inputTopic.pipeInput(“ 键1”,“ 酒吧rrrr”);
        assertThat(outputTopic.readValue(),equalTo(“ barrrrr”));
        assertThat(outputTopic.isEmpty(), is(true));

        inputTopic.pipeInput("键2", "bar");
        assertThat(outputTopic.isEmpty(), is(true));
    }

我们首先选择Topology我们要测试,创建TopologyTestDriver实例以及TestInputTopicTestOutputTopic对象。

接下来,我们在发送任何数据之前确认输出主题是否为空-assertThat(outputTopic.isEmpty(), is(true));

现在可以使用以下命令将数据/记录发送到输入主题inputTopic.pipeInput("key1", "barrrrr");这是一个同步过程,并触发Topology在这种情况下执行filter由于值的长度大于5,因此将其推送到输出主题。 我们用相同的方式确认assertThat(outputTopic.readValue(), equalTo("barrrrr"));并仔细检查输出主题是否为空

最后,我们发送值bar并确认它没有发送到输出主题,因为它的长度小于5。

flatMap

如本系列第1部分(无状态操作)所述,这是一个flatMap operation

        StreamsBuilder builder = new StreamsBuilder();
        KStream<String, String> stream = builder.stream(INPUT_TOPIC);

        stream.flatMap(new 核心价值Mapper<String, String, Iterable<? extends KeyValue<? extends String, ? extends String>>>() {
            @Override
            public Iterable<? extends KeyValue<? extends String, ? extends String>> apply(String k, String csv) {
                String[] values = csv.split(",");
                return Arrays.asList(values)
                        .stream()
                        .map(value -> new KeyValue<>(k, value))
                        .collect(Collectors.toList());
            }
        }).to(OUTPUT_TOPIC);

在上面的示例中,流中的每个记录获取flatMapped,以便首先将每个CSV(逗号分隔)值拆分为各部分,KeyValue将为CSV字符串的每个部分创建一对。

为了测试这个。

        topology = App.flatMap();
        td = new TopologyTestDriver(topology, config);

        inputTopic = td.createInputTopic(App.INPUT_TOPIC, Serdes.String().serializer(), Serdes.String().serializer());
        outputTopic = td.createOutputTopic(App.OUTPUT_TOPIC, Serdes.String().deserializer(), Serdes.String().deserializer());

        inputTopic.pipeInput("随机", "foo,bar,baz");
        inputTopic.pipeInput("hello", "world,universe");
        inputTopic.pipeInput("hi", "there");

        assertThat(outputTopic.getQueueSize(),equalTo(6L));

        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("random", "foo")));
        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("random", "bar")));
        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("random", "baz")));

        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("hello", "world")));
        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("hello", "universe")));

        assertThat(outputTopic.readKeyValue(), equalTo(new KeyValue<>("hi", "there")));

        assertThat(outputTopic.isEmpty(), is(true));

和往常一样,我们设置所需的测试工具类,并将输入记录推送到输入主题。 例如 对于关键random及其逗号分隔的值foo,bar,baz将被拆分为单独的键值对,即它们将导致三条记录被推送到输出表。 其他输入记录也是如此。

我们确认输出主题中的记录数assertThat(outputTopic.getQueueSize(), equalTo(6L));并验证每个键值对以确认flatMap行为

Stateful operation without State store

这是使用groupByKey followed by 计数并将结果存储在输出主题中

        StreamsBuilder builder = new StreamsBuilder();
        KStream<String, String> stream = builder.stream(INPUT_TOPIC);

        stream.groupByKey()
                .count()
                .toStream()
                .to(OUTPUT_TOPIC);

测试有状态操作与无状态操作没有太大区别。

        topology = App.count();
        td = new TopologyTestDriver(topology, config);

        inputTopic = td.createInputTopic(App.INPUT_TOPIC, Serdes.String().serializer(), Serdes.String().serializer());
        TestOutputTopic<String, Long> ot = td.createOutputTopic(App.OUTPUT_TOPIC, Serdes.String().deserializer(), Serdes.Long().deserializer());

        inputTopic.pipeInput("key1", "value1");
        inputTopic.pipeInput("key1", "value2");
        inputTopic.pipeInput("key2", "value3");
        inputTopic.pipeInput("键3", "value4");
        inputTopic.pipeInput("key2", "value5");

        assertThat(ot.readKeyValue(), equalTo(new KeyValue<String, Long>("key1", 1L)));
        assertThat(ot.readKeyValue(), equalTo(new KeyValue<String, Long>("key1", 2L)));
        assertThat(ot.readKeyValue(), equalTo(new KeyValue<String, Long>("key2", 1L)));
        assertThat(ot.readKeyValue(), equalTo(new KeyValue<String, Long>("key3", 1L)));
        assertThat(ot.readKeyValue(), equalTo(new KeyValue<String, Long>("key2", 2L)));

将各个记录发送到输入主题和输出主题,然后验证计数。 不出所料key1, key2 and key3分别有2个,2个,1个。

Stateful operation with a State store

当拓扑由状态存储组成时,事情变得很有趣。 在此示例中,不是将counds发送到输出主题,而是使用中间状态存储(可以通过Interactive Queries进行查询)

        StreamsBuilder builder = new StreamsBuilder();
        KStream<String, String> stream = builder.stream(INPUT_TOPIC);

        stream.groupByKey().count(Materialized.as("count-store"));

TopologyTestDriver提供对状态存储(键值存储)通过getKeyValueStore。 在将每条记录发送到输入主题后,将验证状态存储计数。assertThat(countStore.get(“ key1”),equalTo(1L));

        topology = App.countWithStateStore();
        td = new TopologyTestDriver(topology, config);

        inputTopic = td.createInputTopic(App.INPUT_TOPIC, Serdes.String().serializer(), Serdes.String().serializer());

        KeyValueStore<String, Long> countStore = td.getKeyValueStore("count-store");

        inputTopic.pipeInput("key1", "value1");
        assertThat(countStore.get("key1"), equalTo(1L));

        inputTopic.pipeInput("key1", "value2");
        assertThat(countStore.get("key1"), equalTo(2L));

        inputTopic.pipeInput("key2", "value3");
        assertThat(countStore.get("key2"), equalTo(1L));

        inputTopic.pipeInput("key3", "value4");
        assertThat(countStore.get("key3"), equalTo(1L));

        inputTopic.pipeInput("key2", "value5");
        assertThat(countStore.get("key2"), equalTo(2L));

请注意,在我们的测试中,我们在每个测试方法中都创建了Topology,TopologyTestDriver,TestInputTopic和TestOutputTopic。 这仅仅是因为我们正在测试不同的拓扑。 如果您使用一堆测试用例作为单个JUnit类的一部分来测试单个拓扑,则可以非常轻松地将其移动到带有注释的设置方法中@之前这样它就可以在每个测试用例开始之前自动运行

目前为止就这样了! 这是简短但希望有用的介绍,用于测试基于Kafka Streams的处理管道。

from: https://dev.to//itnext/how-to-test-kafka-streams-applications-4ceb

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值