Всем привет! Меня зовут Андрей, я 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 mytest

import (
    "context"
    "os"
    "testing"

    "github.com/andrei-polukhin/pgdbtemplate"
)

var tm *pgdbtemplate.TemplateManager

func TestMain(m *testing.M) {
    ctx := context.Background()

    // Функция для формирования строки подключения к любой БД
    connStrFunc := func(dbName string) string {
        return "postgres://user:pass@localhost/" + dbName
    }

    // Создаем провайдер для подключения через стандартный database/sql + pq
    provider := pgdbtemplate.NewStandardConnectionProvider(connStrFunc)

    // Говорим, где лежат наши миграции (SQL-файлы)
    migrator := pgdbtemplate.NewFileMigrationRunner(
        []string{"./migrations"},
        pgdbtemplate.AlphabeticalMigrationFilesSorting, // сортировка по алфавиту
    )

    // Создаем менеджер
    var err error
    tm, err = pgdbtemplate.NewTemplateManager(pgdbtemplate.Config{
        ConnectionProvider: provider,
        MigrationRunner:    migrator,
    })
    if err != nil {
        panic(err)
    }

    // ! ВАЖНО: Инициализируем шаблон (делаем всё ОДИН раз)
    if err := tm.Initialize(ctx); err != nil {
        panic(err)
    }

    // Запускаем тесты
    exitCode := m.Run()

    // Подчищаем за собой (удаляем все тестовые БД и шаблон)
    tm.Cleanup(ctx)
    os.Exit(exitCode)
}

func TestMySuperService(t *testing.T) {
    ctx := context.Background()

    // Каждый тест получает свою собственную, чистую БД!
    testDB, testDBName, err := tm.CreateTestDatabase(ctx)
    if err != nil {
        t.Fatalf("Не удалось создать БД: %v", err)
    }
    defer testDB.Close() // Закрываем подключение
    defer tm.DropTestDatabase(ctx, testDBName) // Удаляем БД

    // А тут уже ваш тест...
    repo := NewUserRepository(testDB)
    user, err := repo.CreateUser("ivan@example.com")
    if err != nil {
        t.Errorf("Ошибка создания пользователя: %v", err)
    }
    // ... ваши assertions
}

Цифры говорят сами за себя

Я провел детальные бенчмарки, сравнивая традиционный подход и подход с шаблонами. Результаты впечатляют:

? Сравнение скорости (меньше — лучше)

Сложность схемы

Классический подход

Через шаблоны

Ускорение

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%

Главный вывод: скорость подхода с шаблонами не зависит от сложности схемы. Пока классический метод будет всё больше замедляться с ростом числа таблиц и индексов, метод с шаблонами остается стабильно быстрым.

Что под капотом?

  1. Инициализация: Создается база-шаблон, на нее один раз накатываются все миграции.

  2. Тестирование: Для каждого теста создается новая база через CREATE DATABASE ... TEMPLATE — это быстрое копирование на уровне файловой системы PostgreSQL.

  3. Очистка: После всех тестов удаляются все созданные тестовые базы и сам шаблон.

Для кого этот инструмент?

  • У Вас больше 10 тестов, связанных с базой данных.

  • Ваша схема данных сложнее 2-3 таблиц.

  • Вы часто запускаете тесты во время разработки.

  • Ваш CI-пайплайн включает этап с интеграционными тестами БД.

  • Вы цените свое время и не хотите ждать лишние 10 секунд при каждом запуске.

Полезные ссылки

Буду рад вашим звёздочкам на GitHub, пул-реквестам и issue! Что думаете о таком подходе? Сталкивались ли с подобной проблемой и как решали её раньше?

Большое спасибо за прочтение поста!

Комментарии (0)


  1. anaxita
    17.09.2025 08:00

    Я что то так и не понял чем это решение отличается от вызова create database from template и drop database в конце?


  1. xcono
    17.09.2025 08:00

    Также можно взглянуть на https://github.com/peterldowns/pgtestdb - проекты очень похожи.