Проблема, которая привела к созданию монолита, была классической: команда разрабатывала экосистему из 15 микросервисов (платежи, пользователи, уведомления, отчеты и т.д.). Интеграционное и end-to-end (E2E) тестирование стало кошмаром. Запуск всех сервисов локально требовал 16 ГБ оперативной памяти и тонкой настройки. Тесты в CI/CD падали из-за неустойчивости тестовых окружений, зависящих от множества внешних зависимостей (базы данных, очереди сообщений, кэши). Время обратной связи по тестам превышало 40 минут. Нужно было решение для быстрой, стабильной и воспроизводимой проверки бизнес-логики.
Решение: команда выделила двухнедельный спринт на создание «Монолита для тестирования» (далее — МДТ). Его архитектурные принципы были следующими:
- **Один процесс, одна база данных (in-memory SQLite).** Все слои приложения (API, бизнес-логика, доступ к данным) упакованы в единый исполняемый модуль на Go (выбор языка вторичен, подошел бы Java, C# или Node.js).
- **Упрощенная, но полная бизнес-логика.** МДТ реализовывал все ключевые пользовательские сценарии (регистрация, создание платежа, просмотр истории), но без сложных оптимизаций, фоновых задач и интеграций с реальными внешними провайдерами. Платежный шлюз был заменен на заглушку (stub), которая детерминированно возвращала успех или ошибку.
- **Программируемое API.** Помимо основного API, МДТ предоставлял специальные управляющие endpoints для управления внутренним состоянием: `/test/setup` — для загрузки фикстур, `/test/teardown` — для очистки базы, `/test/mock-payment-response` — для настройки ответа платежной заглушки.
- **Встроенная документация и валидация контрактов.** МДТ использовал те же OpenAPI-спецификации, что и реальные сервисы, что позволяло использовать его как «источник истины» для контрактов при разработке новых микросервисов.
```go
package main
import (
"database/sql"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)
var db *sql.DB
func main() {
// Инициализация in-memory SQLite
db, _ = sql.Open("sqlite3", ":memory:")
createSchema(db)
r := gin.Default()
// Основное бизнес-API
r.POST("/api/v1/payments", createPayment)
r.GET("/api/v1/users/:id", getUser)
// Специальное API для управления тестами
testGroup := r.Group("/test")
{
testGroup.POST("/setup", setupFixtures)
testGroup.POST("/teardown", teardown)
testGroup.POST("/mock-payment-provider", mockProvider)
}
r.Run(":8080")
}
func createPayment(c *gin.Context) {
// Упрощенная логика, использующая запрограммированный мок
var request PaymentRequest
c.BindJSON(&request)
// Проверка бизнес-правил
// ...
// "Интеграция" с моком платежного шлюза
mockResponse := getCurrentMockResponse()
if mockResponse.Success {
c.JSON(200, PaymentResponse{ID: "test_pmt_123", Status: "SUCCESS"})
} else {
c.JSON(402, gin.H{"error": mockResponse.ErrorMessage})
}
}
// Эндпоинт для настройки поведения теста
func mockProvider(c *gin.Context) {
var config MockConfig
c.BindJSON(&config)
setCurrentMockResponse(config)
c.Status(200)
}
```
Опыт внедрения показал ошеломляющие результаты:
* **Скорость:** Время прогона набора из 200 интеграционных тестов сократилось с 40 минут до 45 секунд. Тесты запускались локально у каждого разработчика мгновенно.
* **Стабильность:** Количество ложноположительных срабатываний (flaky tests) упало практически до нуля, так как исчезла зависимость от сетевых задержек и состояния внешних сервисов.
* **Разработка через тестирование (TDD):** Стало возможным практиковать настоящий TDD для API. Разработчик мог написать тест, запустить его против локального МДТ, реализовать фичу в реальном микросервисе и убедиться, что тест проходит против обоих.
* **Обучение новых сотрудников:** МДТ стал бесценным инструментом для онбординга, позволяя понять основные потоки данных в системе за час, а не за неделю.
Ключевой вывод экспертов: «Монолит для тестирования» — это не замена тестированию в продовольственном окружении или E2E-тестам на стейджинге. Это дополнительный, мощный инструмент в пирамиде тестирования, расположенный на уровне интеграционных тестов. Он обеспечивает невероятно быструю обратную связь по корректности бизнес-логики и взаимодействию компонентов, освобождая время и ресурсы для более сложных проверок производительности, безопасности и устойчивости в условиях, приближенных к реальности.
Комментарии (13)