Интеграционное тестирование с системами источников событий - PullRequest
2 голосов
/ 09 мая 2019

Я работаю над PoC, где мы используем CQRS в сочетании с Event Sourcing. В качестве набора инструментов мы используем инфраструктуру Axon и сервер Axon.

У нас есть несколько микросервисов (пакетов Maven) с некоторой бизнес-логикой.

Простой обзор потока приложений:

Мы публикуем сообщение xml (с REST) ​​в службу 1, что приведет к событию (с Aggregate). Сервис 2 обрабатывает событие, «запущенное» сервисом 1, и запускает сагу. Часть потока мудреца, например, для отправки почтового сообщения.

Я могу провести несколько тестов с помощью Axon Test, чтобы протестировать агрегат из сервиса 1 или сагу из сервиса 2. Но есть ли хороший вариант для проведения реального интеграционного теста, когда мы начинаем с отправки сообщения в интерфейс REST и проверки все операции в совокупности и саге (включая отправку почты и т. д.)

Может быть, такого рода интеграционный тест устарел, и лучше тестировать каждый компонент самостоятельно. Я сомневаюсь, что нужно / лучшее решение для тестирования систем такого типа.

1 Ответ

4 голосов
/ 10 мая 2019

Предлагаю взглянуть на тестконтейнеры (https://www.testcontainers.org/)

Он предоставляет очень удобный способ запуска и чистого демонтажа док-контейнеров в тестах JUnit. Эта функция очень полезна для тестирования интеграции приложений с реальными базами данных и любыми другими ресурсами (например, Axon Server), для которых доступен образ докера (https://hub.docker.com/r/axoniq/axonserver/).

).

Я делюсь некоторыми фрагментами кода из тестового класса JUnit 4 (Kotlin). Надеемся, что это может помочь вам начать и развивать свою конкретную стратегию тестирования (интеграция должна охватывать меньшую область, чем сквозные тесты). Мое мнение таково, что интеграционные тесты должны быть направлены на компоненты API обмена сообщениями Axon и REST API по отдельности / независимо. Сквозное соединение должно охватывать все компоненты в вашем микросервисе / ах.

@RunWith(SpringRunner::class)
@SpringBootTest
@ContextConfiguration(initializers = [DrestaurantCourierCommandMicroServiceIT.Initializer::class])
internal class DrestaurantCourierCommandMicroServiceIT {

    @Autowired
    lateinit var eventStore: EventStore

    @Autowired
    lateinit var commandGateway: CommandGateway

    companion object {

        // An Axon Server container
        @ClassRule
        @JvmField
        var axonServerTestContainer = KGenericContainer(
                "axoniq/axonserver")
                .withExposedPorts(8024, 8124)
                .waitingFor(Wait.forHttp("/actuator/info").forPort(8024))
                .withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS))

        // A PostgreSQL container is being started up using a JUnit Class Rule which gets triggered before any of the tests are run:

        @ClassRule
        @JvmField
        var postgreSQLContainer = KPostgreSQLContainer(
                "postgres:latest")
                .withDatabaseName("drestaurant")
                .withUsername("demouser")
                .withPassword("thepassword")
                .withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS))
    }

    // Pass details on the application as properties BEFORE Spring starts creating a test context for the test to run in:
    class Initializer : ApplicationContextInitializer<ConfigurableApplicationContext> {

        override fun initialize(configurableApplicationContext: ConfigurableApplicationContext) {
            val values = TestPropertyValues.of(
                    "spring.datasource.url=" + postgreSQLContainer.jdbcUrl,
                    "spring.datasource.username=" + postgreSQLContainer.username,
                    "spring.datasource.password=" + postgreSQLContainer.password,
                    "spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect",
                    "axon.axonserver.servers=" + axonServerTestContainer.containerIpAddress + ":" + axonServerTestContainer.getMappedPort(8124)
            )
            values.applyTo(configurableApplicationContext)
        }
    }


    @Test
    fun `restaurant command microservice integration test - happy scenario`() {

        val who = "johndoe"
        val auditEntry = AuditEntry(who, Calendar.getInstance().time)
        val maxNumberOfActiveOrders = 5
        val name = PersonName("Ivan", "Dugalic")
        val orderId = CourierOrderId("orderId")

        // ******* Sending the `createCourierCommand` ***********
        val createCourierCommand = CreateCourierCommand(name, maxNumberOfActiveOrders, auditEntry)
        commandGateway.sendAndWait<Any>(createCourierCommand)
        await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
            val latestCourierCreatedEvent = eventStore.readEvents(createCourierCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierCreatedEvent
            assertThat(latestCourierCreatedEvent.name).isEqualTo(createCourierCommand.name)
            assertThat(latestCourierCreatedEvent.auditEntry.who).isEqualTo(createCourierCommand.auditEntry.who)
            assertThat(latestCourierCreatedEvent.maxNumberOfActiveOrders).isEqualTo(createCourierCommand.maxNumberOfActiveOrders)
        }

        // ******* Sending the `createCourierOrderCommand` **********
        val createCourierOrderCommand = CreateCourierOrderCommand(orderId, auditEntry)
        commandGateway.sendAndWait<Any>(createCourierOrderCommand)
        await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
            val latestCourierOrderCreatedEvent = eventStore.readEvents(createCourierOrderCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierOrderCreatedEvent
            assertThat(latestCourierOrderCreatedEvent.aggregateIdentifier.identifier).isEqualTo(createCourierOrderCommand.targetAggregateIdentifier.identifier)
            assertThat(latestCourierOrderCreatedEvent.auditEntry.who).isEqualTo(createCourierOrderCommand.auditEntry.who)
        }

        // ******* Assign the courier order to courier **********
        val assignCourierOrderToCourierCommand = AssignCourierOrderToCourierCommand(orderId, createCourierCommand.targetAggregateIdentifier, auditEntry)
        commandGateway.sendAndWait<Any>(assignCourierOrderToCourierCommand)
        await withPollInterval org.awaitility.Duration.ONE_SECOND atMost org.awaitility.Duration.FIVE_SECONDS untilAsserted {
            val latestCourierOrderAssignedEvent = eventStore.readEvents(assignCourierOrderToCourierCommand.targetAggregateIdentifier.identifier).asStream().toList().last().payload as CourierOrderAssignedEvent
            assertThat(latestCourierOrderAssignedEvent.aggregateIdentifier.identifier).isEqualTo(assignCourierOrderToCourierCommand.targetAggregateIdentifier.identifier)
            assertThat(latestCourierOrderAssignedEvent.auditEntry.who).isEqualTo(assignCourierOrderToCourierCommand.auditEntry.who)
            assertThat(latestCourierOrderAssignedEvent.courierId.identifier).isEqualTo(assignCourierOrderToCourierCommand.courierId.identifier)
        }

    }
}

class KGenericContainer(imageName: String) : GenericContainer<KGenericContainer>(imageName)
class KPostgreSQLContainer(imageName: String) : PostgreSQLContainer<KPostgreSQLContainer>(imageName)
...