Ответ
Выбор подхода зависит от типа тестов. Для модульных тестов стараюсь максимально изолировать код от реальной БД, а для интеграционных — работаю с тестовыми экземплярами.
1. Модульные тесты (Изоляция)
Использую моки (Mock) или стабы (Stub) для абстракций доступа к данным (например, IRepository). Это самый быстрый и надежный способ.
Пример с Moq (C#):
[Fact]
public void GetUser_ShouldReturnUser()
{
// Arrange
var mockRepo = new Mock<IUserRepository>();
var expectedUser = new User { Id = 1, Name = "Test" };
mockRepo.Setup(repo => repo.GetById(1)).Returns(expectedUser);
var service = new UserService(mockRepo.Object);
// Act
var result = service.GetUser(1);
// Assert
Assert.Equal(expectedUser, result);
mockRepo.Verify(repo => repo.GetById(1), Times.Once); // Проверяем вызов
}
2. Интеграционные тесты (Работа с БД)
Здесь нужна реальная или приближенная к реальной СУБД.
- InMemory Provider (EF Core): Быстро, но неполноценно. Подходит только для простой проверки логики, не имитирует специфичные для БД функции (ограничения, транзакции, индексы).
var options = new DbContextOptionsBuilder<AppDbContext>() .UseInMemoryDatabase(databaseName: "TestDb") .Options; - Локальная файловая БД (SQLite): Лучшая эмуляция, особенно в режиме
in-memory. Поддерживает многие функции реальной СУБД.var connection = new SqliteConnection("DataSource=:memory:"); connection.Open(); var options = new DbContextOptionsBuilder<AppDbContext>() .UseSqlite(connection) .Options; - TestContainers (Docker): Золотой стандарт для интеграционных тестов. Запускает реальную БД (PostgreSQL, MySQL) в Docker-контейнере. Максимально приближено к production.
// Пример на Java @Container static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");
3. Ключевые практики
- Изоляция тестов: Каждый тест должен работать со своим набором данных. Использую транзакции с откатом (
TransactionScope) или очистку БД перед каждым тестом. - Идемпотентность: Тесты должны давать одинаковый результат при многократном запуске.
- Отдельная тестовая БД: Никогда не тестирую на production или shared-базе. Использую отдельный инстанс, управляемый через инфраструктуру как код.
Ответ 18+ 🔞
А, ну это же классическая дилемма, блядь! Ты сидишь, думаешь: как бы так протестировать, чтобы и быстро было, и чтоб потом на проде не вылезло, что ты мудак, который про транзакции забыл. Ёпта, история старая, как мир.
Смотри, тут всё просто, как три копейки. Всё упирается в то, что ты тестируешь. Если у тебя чистая бизнес-логика, оторванная от всего — ты её и тестируй в вакууме, как космонавта. Зачем ей реальная база? Она её нихуя не знает. Берёшь моки, подсовываешь ей фейковые данные и смотришь, не обосрётся ли она. Это как проверить, работает ли двигатель, не заводя всю машину.
Вот смотри, пример, чтоб понятнее было. Допустим, у тебя сервис, который юзеров ищет. Ну и что он делает? Ходит в репозиторий. А репозиторий — это просто интерфейс, абстракция, болтушка. Так давай ему в рот такую болтушку и засунем, какую нам надо!
[Fact]
public void GetUser_ShouldReturnUser()
{
// Подготовка (Arrange) - накрываем стол для подставы
var mockRepo = new Mock<IUserRepository>(); // Это и есть наша липовая болтушка
var expectedUser = new User { Id = 1, Name = "Test" }; // А это кукла, которую будем подсовывать
mockRepo.Setup(repo => repo.GetById(1)).Returns(expectedUser); // Говорим болтушке: "Слышь, когда спросят про единичку — отдавай эту куклу!"
var service = new UserService(mockRepo.Object); // А вот наш испытуемый, ему и впариваем липу
// Действие (Act) - нажимаем на кнопку
var result = service.GetUser(1);
// Проверка (Assert) - смотрим, не обманули ли нас
Assert.Equal(expectedUser, result); // Та ли кукла вернулась?
mockRepo.Verify(repo => repo.GetById(1), Times.Once); // И главное — проверим, а обращались ли к нашей болтушке ровно один раз? А то мало ли, она там ещё куда-нибудь позвонила!
}
Видишь? Никакой базы. Чистая симуляция. Быстро, как угорелый. Это для модульных тестов — самое то.
Но, блядь, это же полкартины! А если твоя логика завязана на каких-нибудь хитрожопых JOIN'ах, оконных функциях, или там ограничения целостности на уровне базы? Вот тут моки тебе как мёртвому припарка. Ты протестируешь свою красивую логику, запустишь на прод, а там тебе база: «А я на такое согласия не давала!» И пиздец.
Для этого нужны интеграционные тесты. Тут уже надо базу эмулировать. И есть варианты, от дешёвых до пафосных.
-
InMemory Provider в EF Core. Это как тренироваться воевать на детском батуте. Быстро, мягко, безопасно. Но, сука, он нихуя не повторяет поведение реальной базы. У него нет уникальных индексов, каскадного удаления, сложных типов данных. Он для простейших сценариев: «вставил — считал». Использовать можно, но понимая, что это игрушка.
-
SQLite в памяти. Вот это уже серьёзнее. Почти настоящая SQL-база, только в оперативке. Много чего умеет, многие сценарии можно проверить. Отличный компромисс между скоростью и адекватностью. Многие на нём и останавливаются.
-
TestContainers (через Docker). А это, блядь, тяжёлая артиллерия. Золотой стандарт, ебать его в сраку. Ты говоришь: «Хочу PostgreSQL 16-й версии». И он тебе в Docker'е поднимает самый настоящий постгрес, с погонами и усами. Ты против него тесты гоняешь, и ты на 99% уверен, что на проде будет так же. Это максимально близко к бою. Но и возни с ним, конечно, овердохуища.
И главное, запомни, как «Отче наш»:
- Изоляция тестов: Каждый тест — как первоклассник с собственной партой. Один насрал в свою базу — другому не пахнет. Используй транзакции с откатом или перед каждым тестом вычищай таблицы так, чтобы блестело.
- Идемпотентность: Тест должен быть как кнопка. Нажал сто раз — получил один и тот же результат. Не должно быть: «Ой, а первый раз прошёл, а второй — нет, потому что данные уже есть».
- Отдельная тестовая БД: Это святое. Если ты запускаешь тесты на общей или, упаси боже, продакшен-базе — ты не программист, ты вандал и террорист. Тестовая база должна жить своей жизнью и умирать по твоей команде, не затрагивая никого вокруг.
Вот и вся философия. Для мелкой логики — мокируй и не парься. Для всего, что касается базы всеми фибрами — поднимай максимально похожую среду и гоняй её в хвост и в гриву. И тогда есть шанс, что в проде тебя ждёт не сюрприз в виде пятисотой ошибки, а относительно спокойная жизнь. Ну, насколько это вообще возможно в нашей профессии, конечно.