Всем привет! Меня зовут Андрей, я 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 main
import (
"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! Что думаете о таком подходе? Сталкивались ли с подобной проблемой и как решали её раньше?
Большое спасибо за прочтение поста!