Рубрики
Без рубрики

Интеграционное тестирование на существующих маршрутах с Apache Camel (а также Spring и DBUnit)

Тема и целевая аудитория Эта статья предназначена для того, чтобы показать, как проводить интеграционные тесты… Помечено как java, верблюд, тестирование, весна.

Тема и Целевая аудитория

В этой статье показано, как выполнять интеграционные тесты с Apache Camel в приложении Spring, поскольку в документации это точно не описано. Его могут прочитать как новички, так и разработчики, знакомые с функциями Apache Camel. Тем не менее, немного знаний облегчает понимание.

Вступление

Используя этот метод, вы сможете запустить существующий маршрут от начала до конца (с вашей реальной базой данных или без нее), перехватывая обмен между каждой частью маршрута и проверяя, содержат ли ваши заголовки или тело правильные значения или нет.

Я делал это в классическом проекте Spring с конфигурацией xml и DBUnit для издевательства над базой данных. Надеюсь, это даст вам несколько зацепок.

Установка

Если это еще не сделано в вашем проекте, вам следует добавить тестовые зависимости для camel. Смотрите ниже для пользователей maven:


  org.apache.camel
  camel-test
  ${camel.version}
  test


    org.apache.camel
    camel-test-spring
    ${camel.version}
     test

Пример верблюжьего маршрута

Следующий маршрут имеет простую цель:

  • Сначала он проверяет, присутствует ли объект процесса импорта документа в базе данных, и добавляет его в качестве заголовка exchange
  • Затем он добавляет атрибут документа импорта (который связан с предыдущим процессом импорта документа) в базу данных

Вот код этого маршрута:

@Component
public class TestExampleRoute extends SpringRouteBuilder {

    public static final String ENDPOINT_EXAMPLE = "direct:testExampleEndpoint";

    @Override
    public void configure() throws Exception {
        from(ENDPOINT_EXAMPLE).routeId("testExample")
            .bean(TestExampleProcessor.class, "getImportDocumentProcess").id("getImportDocumentProcess")
            .bean(TestExampleProcessor.class, "createImportDocumentTraitement").id("createImportDocumentTraitement")
            .to("com.pack.camel.routeshowAll=true&multiline=true");
    }

}

идентификатор на маршрутах не является обязательным, вы также можете использовать строки компонента впоследствии. Однако я думаю, что использование идентификаторов можно считать хорошей практикой на случай, если строки вашего маршрутизатора изменятся в будущем.

Пример процессора Camel

Процессор просто содержит только методы, необходимые для маршрута. Это просто классический Java-компонент, содержащий несколько методов. Вы также можете реализовать процессор и переопределить метод обработки.

Смотрите код ниже:

@Component("testExampleProcessor")
public class TestExampleProcessor {

    private static final Logger LOGGER = LogManager.getLogger(TestExampleProcessor.class);

    @Autowired
    public ImportDocumentTraitementServiceImpl importDocumentTraitementService;

    @Autowired
    public ImportDocumentProcessDAOImpl importDocumentProcessDAO;

    @Autowired
    public ImportDocumentTraitementDAOImpl importDocumentTraitementDAO;

    // ---- Constants to name camel headers and bodies
    public static final String HEADER_ENTREPRISE = "entreprise";

    public static final String HEADER_UTILISATEUR = "utilisateur";

    public static final String HEADER_IMPORTDOCPROCESS = "importDocumentProcess";

    public void getImportDocumentProcess(@Header(HEADER_ENTREPRISE) Entreprise entreprise, Exchange exchange) {
        LOGGER.info("Entering TestExampleProcessor method : getImportDocumentProcess");

        Utilisateur utilisateur = SessionUtils.getUtilisateur();
        ImportDocumentProcess importDocumentProcess = importDocumentProcessDAO.getImportDocumentProcessByEntreprise(
                entreprise);

        exchange.getIn().setHeader(HEADER_UTILISATEUR, utilisateur);
        exchange.getIn().setHeader(HEADER_IMPORTDOCPROCESS, importDocumentProcess);
    }

    public void createImportDocumentTraitement(@Header(HEADER_ENTREPRISE) Entreprise entreprise,
            @Header(HEADER_UTILISATEUR) Utilisateur utilisateur,
            @Header(HEADER_IMPORTDOCPROCESS) ImportDocumentProcess importDocumentProcess, Exchange exchange) {
        LOGGER.info("Entering TestExampleProcessor method : createImportDocumentTraitement");

        long nbImportTraitementBefore = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
        ImportDocumentTraitement importDocumentTraitement = this.importDocumentTraitementService.createImportDocumentTraitement(
                entreprise, utilisateur, importDocumentProcess, "md5_fichier_example_test", "fichier_example_test.xml");
        long nbImportTraitementAfter = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();

        exchange.getIn().setHeader("nbImportTraitementBefore", Long.valueOf(nbImportTraitementBefore));
        exchange.getIn().setHeader("nbImportTraitementAfter", Long.valueOf(nbImportTraitementAfter));
        exchange.getIn().setHeader("importDocumentTraitement", importDocumentTraitement);
    }
// Rest of the code contains getters and setters for imported dependencies
}

Здесь особо нечего сказать, кроме того, что мы используем биржу для передачи объектов из одной части в другую. Именно так это обычно делается в моем проекте, поскольку нам приходится обрабатывать действительно сложные процессы.

Тестовый класс верблюда

Тестовый класс будет запускать и запускать тесты на примере маршрута. Мы также используем DBUnit для моделирования базы данных с некоторыми предопределенными значениями.

Во-первых, мы используем абстрактный класс для обмена общими аннотациями между каждым интеграционным тестом Camel. Смотрите код ниже:

@RunWith(CamelSpringRunner.class)
@BootstrapWith(CamelTestContextBootstrapper.class)
@ContextConfiguration(locations = { "classpath:/test-beans.xml" })
@DbUnitConfiguration(dataSetLoader = ReplacementDataSetLoader.class)
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class,
        DbUnitTestExecutionListener.class })
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD) // Not always relevant,  See explanation below
public abstract class AbstractCamelTI {

}

Будьте осторожны, чтобы не забыть какую-либо аннотацию , иначе ваши DAO будут введены неправильно, и это приведет к исключениям нулевого указателя при запуске тестов. Аннотации DBUnit могут быть удалены, если вы используете другую систему управления базами данных.

важный записка: //@DirtiesContext(Режим класса. AFTER_EACH_TEST_МЕТОД) аннотация здесь для перезагрузки контекста верблюда для КАЖДОГО ТЕСТА . Таким образом, каждый метод тестирования имеет новый повторно инициализированный контекст. Но вам также нужно переопределить издевательские конечные точки для каждого теста. Если вам нужно реорганизовать большой процессор camel и протестировать каждую деталь независимо, это может помочь. В противном случае просто удалите эту аннотацию, так как у нее есть несколько недостатков:

  • Тесты больше не являются настоящими интеграционными тестами, и вам приходится удалять и переплетать части маршрута для каждого теста, что может быть немного тяжеловато
  • Перезагрузка контекста для каждого теста занимает некоторое время и может значительно увеличить общее время выполнения тестов. Это может быть неприятно в долгосрочной перспективе

Конфигурация тестового класса

@DatabaseSetup(value = { "/db_data/dao/common.xml", "/db_data/dao/dataForImportDocumentTests.xml" })
public class TestExampleProcessorTest extends AbstractCamelTI {

    @Autowired
    protected CamelContext camelContext;

    @EndpointInject(uri = "mock:catchTestEndpoint")
    protected MockEndpoint mockEndpoint;

    @Produce(uri = TestExampleRoute.ENDPOINT_EXAMPLE)
    protected ProducerTemplate template;

    @Autowired
    ImportDocumentTraitementDAO importDocumentTraitementDAO;

    // -- Variables for tests
    ImportDocumentProcess importDocumentProcess;

    @Override
    @Before
    public void setUp() throws Exception {
        super.setUp();

        importDocumentProcess = new ImportDocumentProcess();
        //specific implementation of your choice
    }
}

Первое испытание

Этот тест запускает первую часть маршрута и приводит его к Макетная точка чтобы мы могли проверить если Процесс импорта документа был правильно выбран и помещен в заголовки:

@Test
public void processCorrectlyObtained_getImportDocumentProcess() throws Exception {
    camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() {

        @Override
        public void configure() throws Exception {
            weaveById("getImportDocumentProcess").after().to(mockEndpoint);
        }
    });

    // -- Launching the route
    camelContext.start();
    template.sendBodyAndHeader(null, "entreprise", company);

    mockEndpoint.expectedMessageCount(1);
    mockEndpoint.expectedHeaderReceived(TestExampleProcessor.HEADER_UTILISATEUR, null);
    mockEndpoint.expectedHeaderReceived(TestExampleProcessor.HEADER_IMPORTDOCPROCESS, importDocumentProcess);
    mockEndpoint.assertIsSatisfied();

    camelContext.stop();
}

Второе испытание

Этот тест просто запускает весь маршрут:

@Test
public void traitementCorrectlyCreated_createImportDocumentTraitement() throws Exception {
    camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() {

        @Override
        public void configure() throws Exception {
            weaveById("createImportDocumentTraitement").after().to(mockEndpoint);
        }
    });

    // -- Launching the route
    camelContext.start();

    Exchange exchange = new DefaultExchange(camelContext);
    exchange.getIn().setHeader(TestExampleProcessor.HEADER_ENTREPRISE, company);
    exchange.getIn().setHeader(TestExampleProcessor.HEADER_UTILISATEUR, null); // No user in this case
    exchange.getIn().setHeader(TestExampleProcessor.HEADER_IMPORTDOCPROCESS, importDocumentProcess);

    long numberOfTraitementBefore = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();

    template.send(exchange);

    mockEndpoint.expectedMessageCount(1);
    mockEndpoint.assertIsSatisfied();

    camelContext.stop();

    long numberOfTraitementAfter = this.importDocumentTraitementDAO.countNumberOfImportDocumentTraitement();
    assertEquals(numberOfTraitementBefore + 1L, numberOfTraitementAfter);
}

Передовая практика

Если вы хотите избежать траты времени на простые ошибки, можно следовать некоторым рекомендациям.

Идентификаторы маршрутов

В этом примере я использую следующую строку, чтобы получить свои маршруты и связать свои конечные точки тестирования:

camelContext.getRouteDefinitions().get(0).adviceWith(camelContext, new AdviceWithRouteBuilder() { [...] });

ОДНАКО Лучшим способом было бы изменить его на ваши маршруты и использовать следующую строку, чтобы получить и сплести их:

camelContext.getRouteDefinition("routeId").adviceWith(camelContext, new AdviceWithRouteBuilder() {  [...]  });

Этот метод немного чище, так как вам не нужно вручную подтверждать, какой маршрут вы хотите протестировать. Рефакторинг маршрута также немного проще, так как вы можете изменять строки вашего маршрутизатора, не меняя их идентификаторы.

Оригинал: “https://dev.to/matthieusb/integration-testing-on-existing-routes-with-apache-camel-and-spring-and-dbunit”