Автор оригинала: Vlad Mihalcea.
Вступление
Базы данных в памяти, такие как H2 , HSQLDB и Derby , отлично подходят для ускорения интеграционных тестов. Хотя большинство запросов к базам данных могут выполняться с этими базами данных в памяти, многие корпоративные системы используют сложные собственные запросы, которые могут быть протестированы только на реальных производственных реляционных базах данных.
В этом посте я покажу вам, как вы можете запускать интеграционные тесты PostgreSQL и MySQL почти так же быстро, как любая база данных в памяти.
Тесты на гибернацию
Hibernate по умолчанию использует H2, и выполнение всех тестов для модуля документации (317 тестов) занимает около 46 секунды:
> gradle clean test :documentation:processTestResources :documentation:testClasses :documentation:test BUILD SUCCESSFUL Total time: 46.148 secs
MySQL
Теперь давайте посмотрим, сколько времени требуется для выполнения всех этих тестов в моем локальном ядре базы данных MySQL 5.7:
> gradle clean test -Pdb=mysql :documentation:processTestResources :documentation:testClasses :documentation:test BUILD SUCCESSFUL Total time: 30 mins 26.568 secs
Операторы DDL MySQL очень дороги, и каждый модульный тест создает и уничтожает SessionFactory
, который, в свою очередь, создает и уничтожает схему базы данных. Это позволяет каждому тесту начинаться с чистого состояния, обеспечивая тем самым изоляцию теста.
Однако по умолчанию все транзакции являются ACID, и для обеспечения долговечности все изменения необходимо сбрасывать на диск всякий раз, когда транзакция завершена. Создание и удаление схемы базы данных после каждого теста требует большого количества интенсивных операций ввода-вывода, которые влияют на общее время выполнения теста.
К счастью, для интеграционных тестов нам не нужна гарантия долговечности. Нам нужна только скорость!
Тем не менее, мы можем переместить каталог данных базы данных на диск оперативной памяти. В Linux вы можете использовать temps , но , поскольку у меня машина с Windows, я собираюсь использовать для этой цели драйвер виртуального диска ImDisk.
Если вы заинтересованы в ускорении тестов интеграции баз данных с Docker и tmpfs, ознакомьтесь с этой статьей . Он работает в любой операционной системе (Linux, OSX, Windows), и даже для Windows работать с ним намного проще, чем с драйвером виртуального диска ImDisk.
Драйвер виртуального диска ImDisk позволяет сопоставлять фрагмент общей оперативной памяти так же, как и жесткий диск.
Сценарий, который выполняет всю работу, выглядит следующим образом:
sc stop %MYSQL_SERVICE% imdisk -D -m R: imdisk -a -s 2G -m R: -P -p "/FS:NTFS /C /Y" mkdir R:\data xcopy "%MySQL_DATA%\data" "R:\data" /S /E "%MySQL_HOME%\bin\mysqld" --defaults-file="%MySQL_DATA%\my_ram.ini"
- Во-первых, я останавливаю службу MySQL по умолчанию.
- Затем я сопоставляю 2 Гб ОЗУ (например, R:\ ) и форматирую его как NTFS.
- После этого я копирую папку данных MySQL на новый диск в памяти.
- Наконец, я просто запускаю новый демон MySQL, используя файл конфигурации, в котором каталог данных настроен следующим образом:
# Path to the database root datadir=R:/data
Когда я закончу тестирование, чтобы остановить демона и запустить предыдущую службу MySQL, я могу запустить следующий пакетный сценарий:
"%MySQL_HOME%\bin\mysqladmin" -u mysql -p shutdown imdisk -D -m R: sc start %MYSQL_SERVICE%
Теперь выполнение тестов в модуле документации Hibernate занимает менее 2 минут:
> gradle clean test -Pdb=mysql :documentation:processTestResources :documentation:testClasses :documentation:test BUILD SUCCESSFUL Total time: 1 mins 41.022 secs
Мы можем сделать лучше, чем это. Как уже объяснялось ранее, нам вообще не нужна долговечность, поэтому я собираюсь изменить некоторые конфигурации MySQL, которые описаны в этой очень хорошо написанной статье Percona :
log-output=NONE slow-query-log=0 innodb_flush_log_at_trx_commit=2 innodb_log_buffer_size=3M innodb_buffer_pool_size=180M
Повторите наши тесты, и мы получим:
Total time: 1 mins 30.628 secs
Это очень 20 улучшение времени по сравнению с конфигурацией ядра базы данных MySQL по умолчанию.
PostgreSQL
Конечно, это не ограничивается MySQL. Фактически, мы можем применить ту же логику к любой реляционной базе данных, которая поддерживает пользовательскую конфигурацию каталога данных.
Запуск тестов документации на PostgreSQL, берет на себя 3 минуты с использованием настроек по умолчанию:
> gradle clean test -Pdb=pgsql :documentation:processTestResources :documentation:testClasses :documentation:test BUILD SUCCESSFUL Total time: 3 mins 23.471 secs
Чтобы запустить новый демон PostgreSQL, работающий на диске в памяти, нам нужно использовать следующий пакетный сценарий:
sc stop %PGSQL_SERVICE% imdisk -D -m R: imdisk -a -s 2G -m R: -P -p "/FS:NTFS /C /Y" mkdir R:\data xcopy "%PGSQL_DATA%" "R:\data" /S /E "%PGSQL_HOME%\bin\pg_ctl" start -D R:\data
Когда мы закончим тестирование, мы сможем остановить демона PostgreSQL и запустить службу по умолчанию следующим образом:
"%PGSQL_HOME%\bin\pg_ctl" stop -D R:\data imdisk -D -m R: sc start %PGSQL_SERVICE%
Повторяя тесты документации, мы получаем следующие результаты:
> gradle clean test -Pdb=pgsql :documentation:processTestResources :documentation:testClasses :documentation:test BUILD SUCCESSFUL Total time: 1 mins 45.431 secs
Как и в случае с MySQL, мы можем улучшить настройки PostgreSQL, как описано в этом посте . Для этого нам нужно изменить файл postgresql.conf следующим образом:
fsync = off synchronous_commit = off full_page_writes = off
Нам также необходимо изменить сценарий запуска, чтобы мы также скопировали новый postgresql.conf в папку данных в памяти:
sc stop %PGSQL_SERVICE% imdisk -D -m R: imdisk -a -s 2G -m R: -P -p "/FS:NTFS /C /Y" mkdir R:\data xcopy "%PGSQL_DATA%" "R:\data" /S /E xcopy postgresql.conf "R:\data" /Y "%PGSQL_HOME%\bin\pg_ctl" start -D R:\data
На этот раз мы получаем следующие результаты:
Total time: 1 mins 37.935 secs
Это решение не ограничивается только ОС Windows. Вы можете достичь той же цели с помощью Docker и tmpfs
в любой операционной системе. Для получения более подробной информации ознакомьтесь с этой статьей .
Модуль документации крошечный по сравнению с hibernate-core , который в настоящее время содержит 4352 модульных теста. С учетом этих оптимизаций выполнение тестов hibernate-core занимает:
5 минут 34,711 секунды | 7 минут 55,082 секунды | 8 минут 34,275 секунды |
Вывод
Хотя и не так быстро, как H2, при использовании накопителя оперативной памяти интеграционные тесты MySQL и PostgreSQL выполняются достаточно быстро. Счастливого тестирования!
Код доступен на GitHub .