> Пишете ли вы unit и интеграционные тесты (PHP)
Уровень: senior · Роль: backend · Язык: PHP · Категория: Технические вопросы
Компании: Travelata
Стек: PHP, C# /.NET
> Пример ответа
Короткий ответ
Да, пишу и unit, и интеграционные тесты, но с разным приоритетом. Unit-тесты покрывают изолированную бизнес-логику и чистые функции, интеграционные - взаимодействие с внешними зависимостями: БД, Redis, API, файловой системой. В PHP-проектах чаще использую PHPUnit и Pest, в C# - xUnit и NUnit. Стараюсь придерживаться принципа тестовой пирамиды: много быстрых unit-тестов, меньше интеграционных, ещё меньше end-to-end.
Подробное объяснение
Unit-тесты проверяют один класс или метод в изоляции, с моками всех зависимостей. Они быстрые, стабильные и помогают быстро находить регрессии при рефакторинге. Интеграционные тесты проверяют совместную работу нескольких компонентов: например, репозиторий с реальной БД, сервис с кэшем, контроллер с middleware.
Для PHP типичный стек: PHPUnit, Mockery для моков, Testbench для Laravel-пакетов, RefreshDatabase для тестов с БД. Для C#: xUnit, Moq или NSubstitute, WebApplicationFactory для интеграционных тестов ASP.NET Core, Testcontainers для Docker-зависимостей.
Ключевой trade-off: скорость против достоверности. Unit-тесты почти мгновенные, но не гарантируют, что код работает с реальными зависимостями. Интеграционные тесты медленнее и требуют окружения, но дают уверенность в корректности связки.
На практике покрываю unit-тестами доменную логику, валидаторы, калькуляторы, сервисы без I/O. Интеграционными - репозитории, миграции, API-эндпоинты, очереди, внешние вызовы через контрактные тесты.
На практике
В реальных проектах соотношение примерно 70/30 в пользу unit-тестов. Для интеграционных тестов использую отдельную тестовую БД, обычно SQLite в памяти для простых случаев или PostgreSQL в Docker для продакшен-совместимости. В C# часто применяю Testcontainers, чтобы поднимать реальный SQL Server или Redis.
Важно, чтобы интеграционные тесты были идемпотентными: каждый тест очищает данные после себя или использует транзакции с откатом. В PHP с Laravel это решается через RefreshDatabase, в C# - через транзакционные фикстуры или очистку таблиц в setup.
Также практикую тесты на миграции: проверяю, что схема БД создаётся и откатывается корректно. Это часто упускают, но именно здесь ломается интеграция с продакшен-окружением.
Пример кода
Пример интеграционного теста на PHP с PHPUnit и Laravel:
PHPpublic function test_order_total_is_calculated_with_discount(): void{$user = User::factory()->create();$product = Product::factory()->create(['price' => 1000]);$order = $this->actingAs($user)->postJson('/api/orders', ['items' => [['product_id' => $product->id, 'quantity' => 2]],'promo_code' => 'SAVE10',])->assertOk()->json('data');$this->assertDatabaseHas('orders', ['id' => $order['id'],'total' => 1800,]);}
Пример unit-теста на C# с xUnit и Moq:
CSHARP[Fact]public void CalculateTotal_AppliesDiscount_WhenThresholdExceeded(){var discountService = new Mock<IDiscountService>();discountService.Setup(x => x.GetDiscount(It.IsAny<decimal>())).Returns(0.1m);var calculator = new OrderCalculator(discountService.Object);var total = calculator.CalculateTotal(1000m);Assert.Equal(900m, total);}
Как отвечать на собеседовании
Начните с прямого ответа "да", затем кратко опишите свой подход. Упомяните, что выбор типа теста зависит от того, что именно проверяется: изолированная логика - unit, связка с инфраструктурой - интеграционные. Расскажите про конкретный стек и инструменты, которыми пользуетесь.
Приведите пример из практики: как интеграционный тест поймал реальный баг, который unit-тесты пропустили, например, несоответствие типов в миграции или неправильный порядок сортировки при выборке из БД. Это покажет, что вы понимаете ценность обоих уровней.
Если спросят про покрытие, не называйте абстрактные проценты. Лучше сказать: "Покрываю критичные бизнес-сценарии и сложную логику, а не гонюсь за метрикой". Упомяните, что используете coverage только как сигнал, а не как цель.
Что проверяет интервьюер
Интервьюер оценивает:
- понимание разницы между unit и интеграционными тестами, их сильных и слабых сторон;
- умение выбирать правильный уровень тестирования для конкретного случая;
- знание инструментов и паттернов: моки, фабрики, транзакционные тесты, testcontainers;
- практический опыт: как вы организуете тестовое окружение, как боретесь с нестабильностью тестов;
- отношение к тестированию в целом: считаете ли вы это обязательной частью разработки или формальностью.
Также смотрят, насколько вы умеете объяснять trade-off между скоростью и достоверностью, и как выстраиваете стратегию тестирования в проекте.
Типичные ошибки
- Говорить "да, пишу тесты", но не уметь объяснить, что именно покрываете и почему.
- Путать unit и интеграционные тесты, называя тесты с БД unit-тестами.
- Использовать моки для всего подряд, включая простые DTO и value objects - это усложняет тесты без пользы.
- Писать интеграционные тесты, которые зависят от внешних сервисов вроде реального API или продакшен-БД - они нестабильны и ломаются без причины.
- Игнорировать тесты на миграции и схемы БД - это частая точка отказа в интеграции.
- Гнаться за 100% покрытием, забывая про качество сценариев.
> Похожие задачи по PHP
Почему Active Record считается антипаттерном
Какие особенности транзакций существуют
Что такое анонимная функция
Что такое нормализация и денормализация баз данных
> Похожие задачи по backend
Почему Active Record считается антипаттерном
Какие особенности транзакций существуют
Что такое анонимная функция
Что такое нормализация и денормализация баз данных
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью