> Какие тесты ты пишешь: функциональные или юнит тесты? (PHP)
Уровень: senior · Роль: backend · Язык: PHP · Категория: Технические вопросы
Компании: vanta
Стек: PHP
> Пример ответа
Короткий ответ
Пишу оба типа, но с чётким приоритетом: юнит-тесты - как основу, функциональные - для критических пользовательских сценариев. В PHP-проектах на Symfony/Laravel это обычно PHPUnit для юнитов и Codeception/Pest+HTTP-тесты для функциональных. Соотношение примерно 70/30, но оно зависит от домена: если бизнес-логика сложная - юнитов больше, если много интеграций - функциональных.
Подробное объяснение
Юнит-тесты изолируют один класс или метод, заменяя зависимости моками. Они быстрые (миллисекунды), стабильные и точно указывают на место поломки. Функциональные тесты проверяют систему целиком: от HTTP-запроса до ответа, включая маршрутизацию, middleware, валидацию, сериализацию. Они медленнее и хрупче, но дают уверенность, что пользовательский сценарий работает.
Выбор зависит от слоя:
- Сервисы, доменные объекты, хелперы - юнит-тесты. Здесь основная бизнес-логика, и её нужно проверять быстро и точечно.
- Контроллеры, API-эндпоинты, CLI-команды - функциональные тесты. Они проверяют связку всех компонентов.
- Репозитории, внешние API, очереди - интеграционные тесты (отдельная категория, но часто их путают с функциональными).
В PHP-стеке типичная структура: tests/Unit и tests/Feature (в Laravel) или tests/Functional (в Symfony). Важно не дублировать: если юнит-тест уже покрыл логику сервиса, функциональный тест не должен перепроверять её детально - только факт, что данные дошли и ответ корректный.
На практике
В реальном проекте я начинаю с юнит-тестов на самые сложные и важные части: расчёты, статусные переходы, обработку граничных значений. Затем добавляю функциональные тесты на ключевые сценарии: регистрация, оформление заказа, выгрузка отчёта. Для legacy-кода, где юнит-тесты писать сложно из-за жёстких зависимостей, функциональные тесты - часто единственный быстрый способ зафиксировать поведение.
Также важно помнить про тестовую базу: функциональные тесты должны работать с транзакциями или сбросом состояния, иначе они становятся нестабильными. Для этого в PHPUnit есть DatabaseTransactions trait, в Pest - uses(RefreshDatabase::class).
Пример кода
Юнит-тест на сервис расчёта скидки:
PHPfinal class DiscountCalculatorTest extends TestCase{public function test_it_applies_percentage_discount(): void{$calculator = new DiscountCalculator();$result = $calculator->calculate(1000, new PercentageDiscount(10));$this->assertSame(900.0, $result);}}
Функциональный тест на API-эндпоинт:
PHPfinal class OrderControllerTest extends TestCase{public function test_user_can_create_order(): void{$user = User::factory()->create();$response = $this->actingAs($user)->postJson('/api/orders', ['product_id' => 1,'quantity' => 2,]);$response->assertStatus(201)->assertJsonPath('data.total', 2000.0);$this->assertDatabaseHas('orders', ['user_id' => $user->id,'total' => 2000.0,]);}}
Как отвечать на собеседовании
Начни с прямого ответа: "пишу оба". Затем объясни критерий выбора: юнит - для бизнес-логики, функциональные - для пользовательских сценариев. Упомяни соотношение и почему оно такое. Приведи конкретный пример из своего опыта: какой баг поймал функциональный тест, а какой - юнит. Покажи, что понимаешь trade-off: скорость против покрытия интеграций. Если спросят про покрытие - скажи, что гоняешься не за процентами, а за значимыми сценариями.
Что проверяет интервьюер
- Понимание разницы между юнит и функциональными тестами, а не просто знание терминов.
- Умение выбирать тип теста под задачу, а не писать всё подряд.
- Практический опыт: знает ли кандидат про транзакции, фабрики, моки в PHPUnit.
- Осознание ограничений: функциональные тесты медленные, юнит-тесты не ловят проблемы интеграции.
- Способность аргументировать: почему в конкретном случае выбрал именно этот тип.
Типичные ошибки
- Ответ "только юнит" - сразу видно отсутствие опыта с реальными проектами, где нужна проверка целых сценариев.
- Ответ "только функциональные" - говорит о том, что кандидат не умеет изолировать логику и пишет медленные тесты на всё подряд.
- Путаница между функциональными и интеграционными тестами - это разные вещи, и интервьюер это проверяет.
- Утверждение "покрытие 100%" - звучит как наивность, опытный разработчик знает, что 100% покрытие не гарантирует отсутствие багов.
- Отсутствие упоминания про скорость и стабильность тестов - это ключевой trade-off, который senior обязан понимать.
> Похожие задачи по PHP
Что такое принцип подстановки Лисков из SOLID?
С какими эволюционными базами данных ты работал
Какие уровни изоляции транзакций существуют и как они помогают избежать проблем с конкурентным доступом?
Что такое транзакции в базах данных и для чего они нужны
> Похожие задачи по backend
Что происходит в контроллере после получения запроса
С какими эволюционными базами данных ты работал
Какие уровни изоляции транзакций существуют и как они помогают избежать проблем с конкурентным доступом?
Что такое транзакции в базах данных и для чего они нужны
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью