Всем привет! Меня зовут Андрей, я Go-разработчик. Сегодня хочу поделиться библиотекой, которая родилась из внутренней боли и желания оптимизировать рабочий процесс.
Проблема: «Ну сколько можно ждать?»
Классический сценарий подготовки базы для интеграционного теста выглядит так:
func TestMyService(t *testing.T) { // 1. Создать новую БД (CREATE DATABASE) // 2. Применить все миграции (N запросов CREATE TABLE, INDEX, FK...) // 3. Запустить сам тест // 4. Удалить БД (DROP DATABASE) // ... и так для КАЖДОГО теста.}
Шаги 1 и 2 повторяются каждый раз, съедая кучу времени. Чем сложнее ваша схема (таблицы, индексы, внешние ключи), тем дольше длится этот процесс.
Решение: Шаблоны (Templates) PostgreSQL
В PostgreSQL есть мощная, но не всегда очевидная фича — шаблонные базы данных (Template Databases). Вы можете создать одну «шаблонную» базу, применить все миграции единожды и сделать ее шаблоном. Все последующие базы создаются командой:
CREATE DATABASE my_fast_test_db TEMPLATE my_template_db;
Эта операция копирует данные на уровне файловой системы и занимает мгновение, независимо от сложности схемы.
Моя библиотека pgdbtemplate автоматизирует всю эту магию, предоставляя простой и удобный API для ваших тестов.
Начинаем работать за 5 минут
Установка стандартная:
go get github.com/andrei-polukhin/pgdbtemplate
А вот так это выглядит в коде ваших тестов:
package mainimport ("context""fmt""log""github.com/andrei-polukhin/pgdbtemplate""github.com/andrei-polukhin/pgdbtemplate-pgx")func main() {// Create a connection provider with pooling options.connStringFunc := func(dbName string) string {return fmt.Sprintf("postgres://user:pass@localhost/%s", dbName)}provider := pgdbtemplatepgx.NewConnectionProvider(connStringFunc)// Create migration runner.migrationRunner := pgdbtemplate.NewFileMigrationRunner([]string{"./migrations"}, pgdbtemplate.AlphabeticalMigrationFilesSorting,)// Create template manager.config := pgdbtemplate.Config{ConnectionProvider: provider,MigrationRunner: migrationRunner,}tm, err := pgdbtemplate.NewTemplateManager(config)if err != nil {log.Fatal(err)}// Initialize template with migrations.ctx := context.Background()if err := tm.Initialize(ctx); err != nil {log.Fatal(err)}// Create test database (fast!).testDB, testDBName, err := tm.CreateTestDatabase(ctx)if err != nil {log.Fatal(err)}defer testDB.Close()defer tm.DropTestDatabase(ctx, testDBName)// Use testDB for testing...log.Printf("Test database %s ready!", testDBName)}
Цифры говорят сами за себя
Я провел детальные бенчмарки, сравнивая традиционный подход и подход с шаблонами. Результаты впечатляют:
🚀 Сравнение скорости (меньше — лучше)
|
Сложность схемы |
Классический подход |
Через шаблоны |
Ускорение |
|---|---|---|---|
|
1 таблица |
28.9 мс |
28.2 мс |
1.03x |
|
3 таблицы |
39.5 мс |
27.6 мс |
1.43x |
|
5 таблиц (+индексы) |
43.1 мс |
28.8 мс |
1.50x |
📈 Массовое создание баз
|
Количество баз |
Классический подход |
Через шаблоны |
Экономия времени |
|---|---|---|---|
|
20 баз |
906.8 мс |
613.8 мс |
32% |
|
50 баз |
2.29 с |
1.53 с |
33% |
|
200 баз |
9.21 с |
5.84 с |
37% |
|
500 баз |
22.31 с |
14.82 с |
34% |
Главный вывод: скорость подхода с шаблонами не зависит от сложности схемы. Пока классический метод будет всё больше замедляться с ростом числа таблиц и индексов, метод с шаблонами остается стабильно быстрым.
Что под капотом?
-
Инициализация: Создается база-шаблон, на нее один раз накатываются все миграции.
-
Тестирование: Для каждого теста создается новая база через
CREATE DATABASE ... TEMPLATE— это быстрое копирование на уровне файловой системы PostgreSQL. -
Очистка: После всех тестов удаляются все созданные тестовые базы и сам шаблон.
Для кого этот инструмент?
-
У Вас больше 10 тестов, связанных с базой данных.
-
Ваша схема данных сложнее 2-3 таблиц.
-
Вы часто запускаете тесты во время разработки.
-
Ваш CI-пайплайн включает этап с интеграционными тестами БД.
-
Вы цените свое время и не хотите ждать лишние 10 секунд при каждом запуске.
Полезные ссылки
-
GitHub репозиторий: github.com/andrei-polukhin/pgdbtemplate
-
Документация (ENG): pkg.go.dev
Буду рад вашим звёздочкам на GitHub, пул-реквестам и issue! Что думаете о таком подходе? Сталкивались ли с подобной проблемой и как решали её раньше?
Большое спасибо за прочтение поста!
ссылка на оригинал статьи https://habr.com/ru/articles/947672/