测试
Spring Cloud Stream 支持在不连接消息系统的情况下测试您的微服务应用。
Spring 集成测试活页夹
Spring Cloud Stream 自带测试绑定器,你可以用它来测试各种应用组件,无需实际的活生生活页夹实现或消息代理。
这个测试装订器作为单元测试和集成测试之间的桥梁,基于Spring Integration框架,作为JVM内的消息代理,基本上给你两全其美——一个真正的活计器,无需网络。
测试活页夹配置
要启用 Spring Integration 测试绑定器,你需要将其作为依赖添加,并用@EnableTestBinder.
添加必需的依赖关系
以下是Maven必填POM条目的示例。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-stream-test-binder</artifactId>
<scope>test</scope>
</dependency>
或者用来做build.gradle.kts
testImplementation("org.springframework.cloud:spring-cloud-stream-test-binder")
测试活页夹的使用
现在你可以把微服务当作简单的单元测试来测试。要启用测试文件夹,请为你的类注释@EnableTestBinder.
@SpringBootTest
public class SampleStreamTests {
@Autowired
private InputDestination input;
@Autowired
private OutputDestination output;
@Test
public void testEmptyConfiguration() {
this.input.send(new GenericMessage<byte[]>("hello".getBytes()));
assertThat(output.receive().getPayload()).isEqualTo("HELLO".getBytes());
}
@SpringBootApplication
@EnableTestBinder
public static class SampleConfiguration {
@Bean
public Function<String, String> uppercase() {
return v -> v.toUpperCase();
}
}
}
如果你需要更多控制,或者想在同一测试套件中测试多个配置,你还可以这样做:
@EnableAutoConfiguration
public static class MyTestConfiguration {
@Bean
public Function<String, String> uppercase() {
return v -> v.toUpperCase();
}
}
. . .
@Test
public void sampleTest() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(
MyTestConfiguration.class))
.run("--spring.cloud.function.definition=uppercase")) {
InputDestination source = context.getBean(InputDestination.class);
OutputDestination target = context.getBean(OutputDestination.class);
source.send(new GenericMessage<byte[]>("hello".getBytes()));
assertThat(target.receive().getPayload()).isEqualTo("HELLO".getBytes());
}
}
对于你有多个绑定和/或多个输入和输出,或者只是想明确名称你发送或接收的目的地,发送()和接收()方法输入目的地和输出目的地被覆盖后,你可以提供输入和输出目的地的名称。
请考虑以下示例:
@EnableAutoConfiguration
public static class SampleFunctionConfiguration {
@Bean
public Function<String, String> uppercase() {
return value -> value.toUpperCase();
}
@Bean
public Function<String, String> reverse() {
return value -> new StringBuilder(value).reverse().toString();
}
}
以及实际测试
@Test
public void testMultipleFunctions() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(
SampleFunctionConfiguration.class))
.run("--spring.cloud.function.definition=uppercase;reverse")) {
InputDestination inputDestination = context.getBean(InputDestination.class);
OutputDestination outputDestination = context.getBean(OutputDestination.class);
Message<byte[]> inputMessage = MessageBuilder.withPayload("Hello".getBytes()).build();
inputDestination.send(inputMessage, "uppercase-in-0");
inputDestination.send(inputMessage, "reverse-in-0");
Message<byte[]> outputMessage = outputDestination.receive(0, "uppercase-out-0");
assertThat(outputMessage.getPayload()).isEqualTo("HELLO".getBytes());
outputMessage = outputDestination.receive(0, "reverse-out-0");
assertThat(outputMessage.getPayload()).isEqualTo("olleH".getBytes());
}
}
对于你有额外映射属性,比如目的地你应该使用这些名称。例如,考虑一个不同的版本前述测试,我们明确映射输入和输出大写函数我的输入和我的输出绑定名称:
@Test
public void testMultipleFunctions() {
try (ConfigurableApplicationContext context = new SpringApplicationBuilder(
TestChannelBinderConfiguration.getCompleteConfiguration(
SampleFunctionConfiguration.class))
.run(
"--spring.cloud.function.definition=uppercase;reverse",
"--spring.cloud.stream.bindings.uppercase-in-0.destination=myInput",
"--spring.cloud.stream.bindings.uppercase-out-0.destination=myOutput"
)) {
InputDestination inputDestination = context.getBean(InputDestination.class);
OutputDestination outputDestination = context.getBean(OutputDestination.class);
Message<byte[]> inputMessage = MessageBuilder.withPayload("Hello".getBytes()).build();
inputDestination.send(inputMessage, "myInput");
inputDestination.send(inputMessage, "reverse-in-0");
Message<byte[]> outputMessage = outputDestination.receive(0, "myOutput");
assertThat(outputMessage.getPayload()).isEqualTo("HELLO".getBytes());
outputMessage = outputDestination.receive(0, "reverse-out-0");
assertThat(outputMessage.getPayload()).isEqualTo("olleH".getBytes());
}
}
测试绑定器和可轮询消息源
Spring Integration Test Binder 还允许你在工作时编写测试可投票消息源(详情请参见[使用民调消费者])
但重要的是要明白,民调不是事件驱动的,而且可投票消息源是一种策略,用于生成(轮询)消息(单数)。你轮询的频率、使用多少线程、从哪里轮询(消息队列还是文件系统)完全由你决定;换句话说,配置轮询器、线程或消息的实际源头是你的责任。幸运的是,Spring 有大量抽象功能可以配置这些。
让我们来看这个例子:
@Test
public void samplePollingTest() {
ApplicationContext context = new SpringApplicationBuilder(SamplePolledConfiguration.class)
.web(WebApplicationType.NONE)
.run("--spring.jmx.enabled=false", "--spring.cloud.stream.pollable-source=myDestination");
OutputDestination destination = context.getBean(OutputDestination.class);
System.out.println("Message 1: " + new String(destination.receive().getPayload()));
System.out.println("Message 2: " + new String(destination.receive().getPayload()));
System.out.println("Message 3: " + new String(destination.receive().getPayload()));
}
@EnableTestBinder
@EnableAutoConfiguration
public static class SamplePolledConfiguration {
@Bean
public ApplicationRunner poller(PollableMessageSource polledMessageSource, StreamBridge output, TaskExecutor taskScheduler) {
return args -> {
taskScheduler.execute(() -> {
for (int i = 0; i < 3; i++) {
try {
if (!polledMessageSource.poll(m -> {
String newPayload = ((String) m.getPayload()).toUpperCase();
output.send("myOutput", newPayload);
})) {
Thread.sleep(2000);
}
}
catch (Exception e) {
// handle failure
}
}
});
};
}
}
上述(非常基础的)示例将生成3条消息,每隔2秒,发送到输出目的地源该活页夹发送给输出目的地我们获取它们(用于任何断言)。目前,它打印如下内容:
Message 1: POLLED DATA
Message 2: POLLED DATA
Message 3: POLLED DATA
如你所见,数据是相同的。这是因为这个绑订器定义了实际 的默认实现消息源- 源消息从中轮询,使用poll()操作。 虽然这对大多数测试场景来说已经足够了,但有些情况下你可能想自己定义消息源. 只需配置一个类型的豆子消息源在你的测试配置中,提供你自己的消息源源实现。
以下是示例:
@Bean
public MessageSource<?> source() {
return () -> new GenericMessage<>("My Own Data " + UUID.randomUUID());
}
输出如下;
Message 1: MY OWN DATA 1C180A91-E79F-494F-ABF4-BA3F993710DA
Message 2: MY OWN DATA D8F3A477-5547-41B4-9434-E69DA7616FEE
Message 3: MY OWN DATA 20BF2E64-7FF4-4CB6-A823-4053D30B5C74
千万别给这颗豆子起名字消息来源因为它将与同名(不同类型)的豆子发生冲突由Spring Boot提供,原因无关。 |