为JUnit 4/5创建一个用于在测试中初始化和注入对象的注解。

huangapple go评论105阅读模式
英文:

Creating an annotation for JUnit 4/5 to initialize and inject an object in tests

问题

我正在为 Kafka 开发一个测试库,Kafkaesque。该库允许您使用流畅而优雅的 API 为 Kafka 开发集成测试。目前,我正在为 Spring Kafka 开发版本。

该库需要在每个测试中进行初始化:

@Test
void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
  kafkaTemplate.sendDefault(1, "data1");
  kafkaTemplate.sendDefault(2, "data2");
  new SpringKafkaesque(broker)
      .<Integer, String>consume()
      .fromTopic(CONSUMER_TEST_TOPIC)
      .waitingAtMost(1L, TimeUnit.SECONDS)
      .waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
      .withDeserializers(new IntegerDeserializer(), new StringDeserializer())
      .expecting()
      .havingRecordsSize(2)
      .assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
      .andCloseConsumer();
}

与手动初始化 SpringKafkaesque 对象不同,我希望创建一个可以为我执行这些操作的注释。类似于 Spring Kafka 的 @EmbeddedKafka 注释。

@SpringBootTest(classes = {TestConfiguration.class})
@Kafkaesque(
    topics = {SpringKafkaesqueTest.CONSUMER_TEST_TOPIC, SpringKafkaesqueTest.PRODUCER_TEST_TOPIC})
class SpringKafkaesqueTest {
  @Autowired
  private Kafkaesque kafkaesque;

  @Test
  void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
    kafkaTemplate.sendDefault(1, "data1");
    kafkaTemplate.sendDefault(2, "data2");
    kafkaesque
        .<Integer, String>consume()
        .fromTopic(CONSUMER_TEST_TOPIC)
        .waitingAtMost(1L, TimeUnit.SECONDS)
        .waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
        .withDeserializers(new IntegerDeserializer(), new StringDeserializer())
        .expecting()
        .havingRecordsSize(2)
        .assertingThatPayloads(Matchers.containsInAnyOrder("data1", "data2"))
        .andCloseConsumer();
   }
}

是否有可能?有任何建议吗?

英文:

I am developing a testing library for Kafka, Kafkaesque. The library lets you develop integration tests for Kafka using a fluid and elegant (?!) API. For now, I develop the version for Spring Kafka.

The library needs to be initialized in every test:

 @Test
 void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
   kafkaTemplate.sendDefault(1, &quot;data1&quot;);
   kafkaTemplate.sendDefault(2, &quot;data2&quot;);
   new SpringKafkaesque(broker)
       .&lt;Integer, String&gt;consume()
       .fromTopic(CONSUMER_TEST_TOPIC)
       .waitingAtMost(1L, TimeUnit.SECONDS)
       .waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
       .withDeserializers(new IntegerDeserializer(), new StringDeserializer())
       .expecting()
       .havingRecordsSize(2)
       .assertingThatPayloads(Matchers.containsInAnyOrder(&quot;data1&quot;, &quot;data2&quot;))
       .andCloseConsumer();
 }

Instead of manually initializing the SpringKafkaesque object, I want to create an annotation that does the magic for me. Something like the @EmbeddedKafka annotation of Spring Kafka.

@SpringBootTest(classes = {TestConfiguration.class})
@Kafkaesque(
    topics = {SpringKafkaesqueTest.CONSUMER_TEST_TOPIC, SpringKafkaesqueTest.PRODUCER_TEST_TOPIC})
class SpringKafkaesqueTest {
  @Autowired
  private Kafkaesque kafkaesque;

  @Test
  void consumeShouldConsumeMessagesProducesFromOutsideProducer() {
    kafkaTemplate.sendDefault(1, &quot;data1&quot;);
    kafkaTemplate.sendDefault(2, &quot;data2&quot;);
    kafkaesque
        .&lt;Integer, String&gt;consume()
        .fromTopic(CONSUMER_TEST_TOPIC)
        .waitingAtMost(1L, TimeUnit.SECONDS)
        .waitingEmptyPolls(5, 100L, TimeUnit.MILLISECONDS)
        .withDeserializers(new IntegerDeserializer(), new StringDeserializer())
        .expecting()
        .havingRecordsSize(2)
        .assertingThatPayloads(Matchers.containsInAnyOrder(&quot;data1&quot;, &quot;data2&quot;))
        .andCloseConsumer();
   }

Is it possible? Any suggestion?

答案1

得分: 1

### JUnit 4
一个可能的解决方案是使用反射创建自定义的注解处理您可以使用 `@Rule` 获取测试方法的名称例如

```java
public class CustomAnnotationTest {
    
    private SpringKafkaesque kafkaesqueInstance;

    @Rule
    public TestName testName = new TestName();

    @Before
    public void init() {
        Method method = null;
        try {
            method = this.getClass().getMethod(testName.getMethodName());
        } catch (Exception ex) {
            // 处理异常
        }
        if (method.isAnnotationPresent(EmbeddedKafka.class)) {
            // 在这里初始化您的 SpringKafkaesque 实例
            // kafkaesqueInstance = new SpringKafkaesque(broker)
            //
        }
    }

    @EmbeddedKafka
    @Test
    public void testCustomAnnotated() {
        // 在这里编写您的测试
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface EmbeddedKafka {
    }
}

您需要将此实例存储在类级变量中。对于没有 @EmbeddedKafka 注解的方法,这个变量将是 null

JUnit 5

对于 JUnit 5,您可以考虑使用 ParameterResolver 进行参数注入。首先,您需要实现这个接口:

public class KafkaesqueResolver implements ParameterResolver {
    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
                                     ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType() == SpringKafkaesque.class;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
                                   ExtensionContext extensionContext) throws ParameterResolutionException {
        // 在这里创建一个 SpringKafkaesque 实例并返回
        return new SpringKafkaesque();
    }
}

接下来,在您的测试类上添加 @ExtendWith(KafkaesqueResolver.class) 注解,并在需要 SpringKafkaesque 实例的测试方法中添加一个参数:

@ExtendWith(KafkaesqueResolver.class)
public class ParamInjectionTest {

    @Test
    public void testNoParams() {
        // 无需注入任何内容
    }

    @Test
    public void testWithParam(SpringKafkaesque instance) {
        // 使用您的实例执行操作
    }
}

在这种情况下,不需要自定义注解。


<details>
<summary>英文:</summary>

### JUnit 4
One possible solution is to create a custom annotation processing using reflection. You can get the test method name with `@Rule`, so for example:

```java
public class CustomAnnotationTest {
    
    private SpringKafkaesque kafkaesqueInstance;

    @Rule
    public TestName testName = new TestName();

    @Before
    public void init() {
        Method method = null;
        try {
            method = this.getClass().getMethod(testName.getMethodName());
        } catch (Exception ex) {
            // handle exceptions
        }
        if (method.isAnnotationPresent(EmbeddedKafka.class)) {
            // Init your SpringKafkaesque instance here
            // kafkaesqueInstance = new SpringKafkaesque(broker)
            //
        }
    }

    @EmbeddedKafka
    @Test
    public void testCustomAnnotated() {
        // your test here
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @interface EmbeddedKafka {
    }
}

You need to store this instance in the class-level variable. For the methods with no @EmbeddedKafka annotation, this variable will be null.

JUnit 5

With JUnit 5 you may consider using parameter injection with ParameterResolver. First of all, you need to implement this interface:

public class KafkaesqueResolver implements ParameterResolver {
    @Override
    public boolean supportsParameter(ParameterContext parameterContext,
                                     ExtensionContext extensionContext) throws ParameterResolutionException {
        return parameterContext.getParameter().getType() == SpringKafkaesque.class;
    }

    @Override
    public Object resolveParameter(ParameterContext parameterContext,
                                   ExtensionContext extensionContext) throws ParameterResolutionException {
        // Create an instance of SpringKafkaesque here and return it
        return new SpringKafkaesque();
    }
}

Next, add @ExtendWith(KafkaesqueResolver.class) annotation to your test class, and add a parameter to your test method, where you need the instance of SpringKafkaesque:

@ExtendWith(KafkaesqueResolver.class)
public class ParamInjectionTest {

    @Test
    public void testNoParams() {
        // nothing to inject
    }

    @Test
    public void testWithParam(SpringKafkaesque instance) {
        // do what you need with your instance
    }
}

No custom annotation required in this case.

huangapple
  • 本文由 发表于 2020年9月19日 20:43:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/63968934.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定