Производительность в тестах

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

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

Менеджмент контекстов

Не очевидные вещи про @DirtiesContext

TestPropertySource

Допустим вы написали тест, все хорошо. Вам нужно написать еще один тест, но с одним не большим отличием в плане контекста - с одним изменененным свойством, или с двуми или … в целом количество свойств ни на что не влияет. Вы написали тест и использовали TestPropertySource и все хорошо. Потом еще раз и еще и еще. И в какой-то момент вы ловите первую ошибку по нехватке памяти. Почему такое произошло? Потому что создаются новые контексты и не чистятся - смотрите баги. Есть механизм лимита не кеши, но … В общем анализ свойств может показать, что контексты оформленные для отдельных тестовых сценариев не переиспользуются - разовые. Давайте из удалять через @DirtyContext.

DynamicTestPropertySource

Refresh properties in runtime

В случае если есть возможность

Статья про cloud config, @RefreshScope - https://habr.com/ru/companies/otus/articles/590761/.

Memory leaks

Memory leak in Spring

Ресурсоемкие методы

Cегодня профилировал тесты и нашел два ресурсоемких метода:

  1. Связан с этой проблемой, знакомой нам уже, - https://stackoverflow.com/questions/76704872/spring-boot-3-high-cpu-usage-on-basicauth NoOpPasswordEncoder for spring auth
  2. Какой-то новый и мне не знакомый
java.lang.invoke.VarHandleByteArrayAsInts$ArrayHandle.index
org.postgresql.shaded.com.ongres.scram.common.util.CryptoUtil.hi
org.postgresql.shaded.com.ongres.scram.common.ScramMechanisms.saltedPassword

На сколько я понял это часть драйвера PostgreSQL JDBC для защиты паролей в процессе аутентификации. Отключить можно указав переменную для контейнера

    private final static PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>(DockerImageName.parse("postgres:14"))
            .withDatabaseName("application")
            .withEnv("POSTGRES_HOST_AUTH_METHOD", "trust") <----- ТУТ
            .withCommand("postgres -c max_connections=300")
            .waitingFor(Wait.forListeningPort());

и

    private void execute(PGSimpleDataSource dataSource) throws SQLException {
        for (int i = 0; i < 100; i++) {
            try (Connection connection = dataSource.getConnection()) {
                connection.prepareStatement("select 1;").execute();
            }
        }
    }

и можно уже не передавать пароль

        PGSimpleDataSource dataSource = new PGSimpleDataSource();
        dataSource.setUser(container.getUsername());
        dataSource.setUrl(container.getJdbcUrl());
        execute(dataSource);

Синтетический бенчмарк показывает прирост в 20% при использовании trust.

Benchmark                                           Mode  Cnt  Score   Error  Units
PostgresqlContainerAuthModeBenchmark.methodDefault    ss   20  2,202 ± 0,049   s/op
PostgresqlContainerAuthModeBenchmark.methodTrust      ss    7  1,552 ± 0,100   s/op

Я еще погонял CI с включенным и выключенным, в целом картина на CI повторяет ту же тенденцию.

Заключение

ideaarticledraft