Представьте проект, в котором уже написано несколько сотен тестов. Каждый тест настраивает базу под себя: добавляет данные, вызывает truncate по окончанию. Хочется запускать тесты параллельно, чтобы ускориться, но если два теста запустить одновременно, они почти наверняка друг другу помешают. В данной статье поделюсь подходом, который позволил решить проблему без рефакторинга. Гугление и LLM-модели предложили следующие решения: Писать тесты так, чтобы они не зависели от количества элементов в базе и не вызывали truncate. Идея хорошая, но если уже есть легаси, переписывать 800+ тестов (именно столько было на проекте) — нереалистичный вариант. Запускать тесты в транзакции и откатывать транзакцию после выполнения. С таким решением знаком лично — проблемы возникают, когда проверить надо именно логику транзакций тестируемых функций. Например, иногда логика теста такова, что транзакция должна упасть, а это повлечет закрытие транзакции теста, что нам не нужно. Другой пример: тест может читать данные из разных потоков; пока висит транзакция без коммита, данные будут недоступны для других потоков. Использовать in-memory db. Это решение не понравилось из-за того, что оно не универсально и подойдет не для любой базы данных. Настоящая база будет работать не так (или вообще не так), как база на тестах. Поддерживать пул с базами данных. Понадобится изолировать доступ к каждой базе из пула, что наверняка потребует большого рефакторинга — в тестах поход в базу может быть где угодно: и из before/after-Each, и из статики, и внутри кода. Создавать копию базы на каждый тест. Звучит как усложнение с просадкой по времени на тест, но именно эта идея натолкнула на решение, о котором будет статья. Если кто-то создает копию базы данных на тест, почему бы не попробовать создать копию на процесс (операционной системы)? Пример с конкретными цифрами для упрощения восприятия: Мы разделим 800 тестов на 4 группы по 200 и запустим каждую группу в отдельном процессе. Каждый процесс будет работать со своей копией базы и запускать тесты последовательно. Параллельность достигается одновременным запуском 4-х процессов. Когда тесты пройдут, мы запустим задачу на удаление созданных копий. 3 шага: Делегируем Gradle запуск нескольких процессов. Создаем копию базы в каждом процессе. Делегируем Gradle запуск класса с очисткой базы. Gradle сам умеет форкать процессы и делить имеющиеся тесты между ними. Нужно в build.gradle модуля в "таске" test указать maxParallelForks. Я поигрался с различными вариантами на рабочем проекте, и остановился на maxParallelForks с половиной от имеющихся CPU. Это оказалось быстрее всего и на CI, и локально. Создать копию базы с уникальным именем можно через добавление к имени базы Process id. В доках Gradle можно найти рекомендацию использовать System.getProperty("org.gradle.test.worker") как уникальный ID на процесс. Другой вариант — использовать библиотеку TestContainers. Ниже привожу пример без использования сторонних инструментов  В build.gradle регистрируем “таску”, которая будет запускать Kotlin-класс и привязываем ее к окончанию тестов:  Вот проект. Для запуска необходим докер: По логам можно увидеть, что создаются копии базы в разных процессах, прогоняются тесты, копии удаляются: В maven все то же самое, только используем forkCount вместо maxParallelForks: Один вариант — добавлять метадату к тестам: И запускать тесты с метадатой в разных процессах: Предложенное решение с запуском тестов в разных процессах ускоряют прогон в несколько раз (в зависимости от ресурсов машины) и при этом практически не требует затрат на рефакторинг. Проект, на котором я это реализовал, локально ускорился в 2 раза, на CI — примерно в 4. Недостаток подхода — неоптимальная трата ресурсов: один процесс может закончить свою группу тестов быстрее других и висеть без дела. На мой взгляд, для проекта с большим количеством тестов, которые меняют базу как хотят, предложенный подход является наиболее привлекательным. Буду рад, если кто-то дополнит в комментариях.