Как писать интеграционные тесты для сложных Java-приложений
Как писать интеграционные тесты для сложных Java-приложений

Недавно у меня появилось время открыть исходный код предыдущего Система обмена мгновенными сообщениямиВзял и продолжил разработку(Действительно, друзья часто убеждали меня обновиться в эти годы.)。

Да, это действительно эти годы, ведь последняя версия вышла в августе 2019 года.

Этот период относительно важениз Просто обновитецентр метаданныхОтдельностоящий,Раньше это было и zookeeper Коды сильно связаны друг с другом, и после рефакторинга может быть несколько реализаций.

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

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

Кроме того, за последние несколько лет я также познакомился со многими отличными проектами с открытым исходным кодом (такими как Pulsar, OpenTelemetry, HertzBeat и т. д.), и все они имеют, прежде всего, полноценные процессы слияния кода; заключается в запуске тестового конвейера.

В сообществе OpenTelemetry это еще строже:

Они очень много выстраивают процессы тестирования.,Включает юнит-тесты, интеграционные тесты, стиль. кодирования、Совместимость с несколькими версиями и т. д.

Итак, объединив опыт этих прекрасных проектов, я также cim В проект добавлены новые связанные модули cim-integration-test,В то же время такжесуществовать github Связанные конфигурации настраиваются на действия, конечный эффект следующий:

существовать “Build with Maven” Этап запускает модульный и интегрированный тест, и результаты теста в конечном итоге будут загружены в Codecov,тогда будетсуществовать PR Выведите отчет об испытаниях в область комментариев.

Соответствующая конфигурация действий выглядит следующим образом:

Просто настройте несколько заданий, основное внимание здесь:

Язык кода:shell
копировать
mvn -B package --file pom.xml

Он скомпилирует и запустит весь тестовый код проекта.

модуль cim-интеграции-теста

Чтобы облегчить интегрированное тестирование, я добавил cim-integration-test В этом модуле нет исходного кода, только код, связанный с тестированием.

Схема наследования класса выглядит следующим образом:

потому что Нам нужно сделать интегрированный тест cim Запущены все зависимые службы, в настоящее время в основном следующие службы:

  • cim-сервер: cim изсервер
  • cim-route: служба маршрутизации
  • cim-клиент: клиент

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

интегрированный test container

Давайте сначала посмотрим на тестовую реализацию сервера:

Язык кода:java
копировать
public abstract class AbstractServerBaseTest {  
  
    private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName  
            .parse("zookeeper")  
            .withTag("3.9.2");  
  
    private static final Duration DEFAULT_STARTUP_TIMEOUT = Duration.ofSeconds(60);  
  
    @Container  
    public final ZooKeeperContainer  
            zooKeeperContainer = new ZooKeeperContainer(DEFAULT_IMAGE_NAME, DEFAULT_STARTUP_TIMEOUT);  
  
    @Getter  
    private String zookeeperAddr;  
  
    public void startServer() {  
        zooKeeperContainer.start();  
        zookeeperAddr = String.format("%s:%d", zooKeeperContainer.getHost(), zooKeeperContainer.getMappedPort(ZooKeeperContainer.DEFAULT_CLIENT_PORT));  
        SpringApplication server = new SpringApplication(CIMServerApplication.class);  
        server.run("--app.zk.addr=" + zookeeperAddr);  
    }  
}

потому что server зависит от zookeeper какцентр метаданных,таксуществоватьзапускать Нужно сделать это раньше zookeeper Запускать.

В это время вам нужно использовать testcontainer Для обеспечения поддержки его можно использовать во время одного теста. docker запускать любую подобную службу существует CI Пройти интегрированный тест в Китае очень просто.

Большая часть промежуточного программного обеспечения, которое мы используем ежедневно, поддерживается и очень проста в использовании.

Сначала добавьте соответствующие зависимости:

Язык кода:xml
копировать
<dependencies>
    <dependency>
        <groupId>org.postgresql</groupId>
        <artifactId>postgresql</artifactId>
        <version>42.7.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.5.6</version>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Затем выберите сервис, на который нам нужно положиться, например PostgreSQL

Язык кода:xml
копировать
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.19.8</version>
    <scope>test</scope>
</dependency>

Затем существуют тесты запуска связанных с сервисом программ в коде.

Язык кода:java
копировать
class CustomerServiceTest {

  static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>(
    "postgres:16-alpine"
  );

  CustomerService customerService;

  @BeforeAll
  static void beforeAll() {
    postgres.start();
  }

  @AfterAll
  static void afterAll() {
    postgres.stop();
  }

  @BeforeEach
  void setUp() {
    DBConnectionProvider connectionProvider = new DBConnectionProvider(
      postgres.getJdbcUrl(),
      postgres.getUsername(),
      postgres.getPassword()
    );
    customerService = new CustomerService(connectionProvider);
  }

Обычно нам нужно получить ссылки на эти промежуточные программы, такие как IP-порты и т. д.

Язык кода:java
копировать
org.testcontainers.containers.ContainerState#getHost
org.testcontainers.containers.ContainerState#getMappedPort

Соответствующие IP и порт обычно получаются с помощью этих двух функций.

интегрированный

Язык кода:java
копировать
@Container  
RedisContainer redis = new RedisContainer(DockerImageName.parse("redis:7.4.0"));  
  
public void startRoute() {  
    redis.start();  
    SpringApplication route = new SpringApplication(RouteApplication.class);  
    String[] args = new String[]{  
            "--spring.data.redis.host=" + redis.getHost(),  
            "--spring.data.redis.port=" + redis.getMappedPort(6379),  
            "--app.zk.addr=" + super.getZookeeperAddr(),  
    };    
    route.setAdditionalProfiles("route");  
    route.run(args);  
}

для route Это не только необходимо zookeeper Все еще нужно Redis Чтобы сохранить отношения маршрутизации пользователя, в это время Все еще нужно запустить Redis Контейнеры используются таким же образом.

Наконец, необходимо springboot Чтобы запустить эти два приложения, мы непосредственно создаем SpringApplication объект, а затем передать параметры, которые необходимо изменить, через --varname=value Передайте данные в форму.

Вы также можете пройти setAdditionalProfiles() Функция указывает текущее запущенное приложение. профиль, чтобы мы могли использовать соответствующий файл конфигурации в тестовом каталоге.

image.png
image.png
Язык кода:java
копировать
route.setAdditionalProfiles("route");  

Например, здесь мы установили его на route ты можешь использовать application-route.yaml как route файл конфигурации, нет необходимости передавать каждый параметр -- прошел дальше.

Язык кода:java
копировать
private void login(String userName, int port) throws Exception {  
    Long userId = super.registerAccount(userName);  
    SpringApplication client = new SpringApplication(CIMClientApplication.class);  
    client.setAdditionalProfiles("client");  
    String[] args = new String[]{  
            "--server.port=" + port,  
            "--cim.user.id=" + userId,  
            "--cim.user.userName=" + userName  
    };  
    client.run(args);  
}  
  
@Test  
public void olu() throws Exception {  
    super.startServer();  
    super.startRoute();  
    this.login("crossoverJie", 8082);  
    this.login("cj", 8182);  
    MsgHandle msgHandle = SpringBeanFactory.getBean(MsgHandle.class);  
    msgHandle.innerCommand(":olu");  
    msgHandle.sendMsg("hello");  
}

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

Такие как здесь olu(oline user) Процесс тестирования:

  • запускать server и route
  • Войдите и зарегистрируйте два аккаунта
  • Опросить всех пользователей
  • Отправить сообщение

Окончательные результаты испытаний соответствуют ожиданиям.

image.png
image.png

Возникшие проблемы

Многоуровневое применение приложений

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

потому чтоклиент、route、server Все они запускаются с точки зрения приложения, и некоторые ключевые показатели получить невозможно.

Например, вывод существует в строке user,Когда клиенткак заявка,онлайн-пользователи просто печатают существующее прямо на терминале,и Нет прямого доступа к интерфейсу для возврата проводных данных, то же самое касается отправки и получения сообщений;

По сути, это все интерфейсы внутри приложения, но они представляют собой единое целое. springboot Приложение не предоставляет таких возможностей.

Существенная проблема в том, что здесь должен быть модуль client-sdk. Клиент также реализован на основе этого sdk, чтобы можно было лучше тестировать связанные функции.

После этого мы собираемся выделить sdk в отдельный модуль, чтобы можно было легко реализовать разные взаимодействия на основе этого sdk и даже сделать UI-интерфейс.

Компиляция не удалась

Другая проблема в том, что я напрямую client/route/server изполагатьсяинтегрированныйприезжать integration-test В модуле:

Язык кода:xml
копировать
<dependency>  
  <groupId>com.crossoverjie.netty</groupId>  
  <artifactId>cim-server</artifactId>  
  <version>${project.version}</version>  
  <scope>compile</scope>  
</dependency>  
  
<dependency>  
  <groupId>com.crossoverjie.netty</groupId>  
  <artifactId>cim-forward-route</artifactId>  
  <version>${project.version}</version>  
  <scope>compile</scope>  
</dependency>  
  
<dependency>  
  <groupId>com.crossoverjie.netty</groupId>  
  <artifactId>cim-client</artifactId>  
  <version>${project.version}</version>  
  <scope>compile</scope>  
</dependency>

существовать IDEA Вы можете напрямую запустить тестовые примеры здесь, нажав кнопку тестирования напрямую, но если вы хотите пройти mvn test Возникла проблема.

image.png
image.png

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

Язык кода:xml
копировать
<plugin>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-maven-plugin</artifactId>
	<executions>
		<execution>
			<goals>
				<goal>repackage</goal>
			</goals>
		</execution>
	</executions>
</plugin>

Эти модули в конечном итоге будут упакованы в springboot из jar пакет, в результате чего integration-test существовать Не удалось загрузиться изииспользовать внутрииздобрый。

Хорошего решения пока не найдено,Мне просто нужно сначала удалить эти плагины.,Если требуется упаковка, укажите плагин вручную.

Язык кода:shell
копировать
mvn clean package spring-boot:repackage -DskipTests=true

На самом деле, основная проблема здесь в том, что нет многоуровневого результата. Лучше всего полагаться на него. route и server из SDK Чтобы протестировать.

сейчассуществоватьпотому что Есть тест из CI Каждый также может внести свой вклад, вы можете посмотреть здесь help want,Для начала есть несколько простых и легких вещей.

https://github.com/crossoverJie/cim/issues/135

Справочные ссылки:

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.