> Какие тесты ты пишешь: функциональные или юнит тесты? (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).

Пример кода

Юнит-тест на сервис расчёта скидки:

PHP
final 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-эндпоинт:

PHP
final 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 обязан понимать.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью