Как писать интеграционные тесты для сложных 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
Неразрушающее увеличение изображений одним щелчком мыши, чтобы сделать их более четкими артефактами искусственного интеллекта, включая руководства по установке и использованию.
boy illustration
Копикодер: этот инструмент отлично работает с Cursor, Bolt и V0! Предоставьте более качественные подсказки для разработки интерфейса (создание навигационного веб-сайта с использованием искусственного интеллекта).
boy illustration
Новый бесплатный RooCline превосходит Cline v3.1? ! Быстрее, умнее и лучше вилка Cline! (Независимое программирование AI, порог 0)
boy illustration
Разработав более 10 проектов с помощью Cursor, я собрал 10 примеров и 60 подсказок.
boy illustration
Я потратил 72 часа на изучение курсорных агентов, и вот неоспоримые факты, которыми я должен поделиться!
boy illustration
Идеальная интеграция Cursor и DeepSeek API
boy illustration
DeepSeek V3 снижает затраты на обучение больших моделей
boy illustration
Артефакт, увеличивающий количество очков: на основе улучшения характеристик препятствия малым целям Yolov8 (SEAM, MultiSEAM).
boy illustration
DeepSeek V3 раскручивался уже три дня. Сегодня я попробовал самопровозглашенную модель «ChatGPT».
boy illustration
Open Devin — инженер-программист искусственного интеллекта с открытым исходным кодом, который меньше программирует и больше создает.
boy illustration
Эксклюзивное оригинальное улучшение YOLOv8: собственная разработка SPPF | SPPF сочетается с воспринимаемой большой сверткой ядра UniRepLK, а свертка с большим ядром + без расширения улучшает восприимчивое поле
boy illustration
Популярное и подробное объяснение DeepSeek-V3: от его появления до преимуществ и сравнения с GPT-4o.
boy illustration
9 основных словесных инструкций по доработке академических работ с помощью ChatGPT, эффективных и практичных, которые стоит собрать
boy illustration
Вызовите deepseek в vscode для реализации программирования с помощью искусственного интеллекта.
boy illustration
Познакомьтесь с принципами сверточных нейронных сетей (CNN) в одной статье (суперподробно)
boy illustration
50,3 тыс. звезд! Immich: автономное решение для резервного копирования фотографий и видео, которое экономит деньги и избавляет от беспокойства.
boy illustration
Cloud Native|Практика: установка Dashbaord для K8s, графика неплохая
boy illustration
Краткий обзор статьи — использование синтетических данных при обучении больших моделей и оптимизации производительности
boy illustration
MiniPerplx: новая поисковая система искусственного интеллекта с открытым исходным кодом, спонсируемая xAI и Vercel.
boy illustration
Конструкция сервиса Synology Drive сочетает проникновение в интрасеть и синхронизацию папок заметок Obsidian в облаке.
boy illustration
Центр конфигурации————Накос
boy illustration
Начинаем с нуля при разработке в облаке Copilot: начать разработку с минимальным использованием кода стало проще
boy illustration
[Серия Docker] Docker создает мультиплатформенные образы: практика архитектуры Arm64
boy illustration
Обновление новых возможностей coze | Я использовал coze для создания апплета помощника по исправлению домашних заданий по математике
boy illustration
Советы по развертыванию Nginx: практическое создание статических веб-сайтов на облачных серверах
boy illustration
Feiniu fnos использует Docker для развертывания личного блокнота Notepad
boy illustration
Сверточная нейронная сеть VGG реализует классификацию изображений Cifar10 — практический опыт Pytorch
boy illustration
Начало работы с EdgeonePages — новым недорогим решением для хостинга веб-сайтов
boy illustration
[Зона легкого облачного игрового сервера] Управление игровыми архивами
boy illustration
Развертывание SpringCloud-проекта на базе Docker и Docker-Compose