← Назад к вопросам

Что означают фейки в модульном (unit) тестировании?

1.8 Middle🔥 131 комментариев
#Тестирование

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI7 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое фейки (Fakes) в модульном тестировании?

В контексте модульного (unit) тестирования фейки (Fakes) — это общий термин, описывающий объекты-заменители (test doubles), которые используются для изоляции тестируемого кода от его зависимостей. Основная цель — создать контролируемую среду для проверки логики отдельного модуля (класса, функции), не полагаясь на реальные, часто медленные, нестабильные или сложные внешние системы (базы данных, API, файловые системы и т.д.).

Роль фейков в архитектуре тестов

Фейки являются ключевым инструментом для соблюдения принципа изоляции — одного из столпов unit-тестов. Они позволяют:

  • Убрать неопределённость: Заменить элемент, который может вести себя непредсказуемо (например, сетевой вызов).
  • Ускорить тесты: Избежать длительных операций ввода-вывода.
  • Сфокусироваться на поведении: Тестировать именно логику модуля, а не корректность работы его зависимостей.
  • Сымитировать сложные сценарии: Легко создать состояния, которые в реальной системе возникают редко или сложно воспроизводимы (например, ошибка базы данных).

Классификация фейков (Test Doubles)

Важно понимать, что "фейк" — это часто собирательное название. Мартин Фаулер и сообщество выделяют несколько более конкретных типов, каждый со своей целью:

  1. Стабы (Stubs) — предоставляют заранее заданные ответы на вызовы во время теста.

    // Реальный сервис отправки почты
    class RealMailer {
        public function send($email, $message) {
            // Сложная логика соединения с SMTP-сервером...
        }
    }
    
    // Стаб для теста
    class MailerStub {
        public function send($email, $message) {
            // Ничего не делает, просто возвращает "успех"
            return true;
        }
    }
    
    // В тесте
    $userService = new UserService(new MailerStub());
    $result = $userService->register('test@mail.com'); // Не зависит от реальной почты
    $this->assertTrue($result);
    
  2. Моки (Mocks) — используются для проверки взаимодействия (behavior verification). Мы заранее задаём ожидания: какие методы должны быть вызваны, с какими аргументами и сколько раз. Фреймворки вроде PHPUnit (через createMock или getMockBuilder), Mockery или Prophecy создают их динамически.

    // Тест с моком (используя PHPUnit)
    public function testUserRegistrationSendsWelcomeEmail(): void
    {
        // 1. Создаём мок зависимого сервиса
        $mailerMock = $this->createMock(MailerInterface::class);
    
        // 2. Задаём ожидание: метод send должен быть вызван ровно 1 раз
        //    с конкретными аргументами
        $mailerMock->expects($this->once())
                   ->method('send')
                   ->with('welcome@example.com', 'Добро пожаловать!');
    
        // 3. Внедряем мок и тестируем
        $userService = new UserService($mailerMock);
        $userService->register('welcome@example.com');
    
        // 4. Проверка ожиданий происходит автоматически в tearDown
    }
    
  3. Спаи (Spies) — похожи на моки, но проверка происходит после выполнения действий (post-validation). Они пассивно записывают вызовы, которые с ними произошли.

    // Пример с использованием Mockery в режиме spy
    public function testServiceLogsError(): void
    {
        $loggerSpy = Mockery::spy(LoggerInterface::class);
        $service = new SomeService($loggerSpy);
    
        $service->performRiskyOperation();
    
        // Проверяем постфактум: был ли вызван метод log с аргументом 'error'
        $loggerSpy->shouldHaveReceived()->log('error');
    }
    
  4. Фейковые объекты (Fakes в узком смысле) — имеют рабочую, но упрощённую реализацию, обычно для состояния (state verification). Они не используются для задания ожиданий, как моки.

    // Реальное UserRepository работает с PostgreSQL
    // Фейковый UserRepository работает с массивом в памяти
    class FakeUserRepository implements UserRepositoryInterface {
        private array $users = [];
    
        public function findById(int $id): ?User {
            return $this->users[$id] ?? null; // Быстрый поиск в массиве
        }
    
        public function save(User $user): void {
            $this->users[$user->getId()] = $user;
        }
    }
    
    // Тест
    public function testUserCanChangeEmail(): void
    {
        $repo = new FakeUserRepository(); // Быстро и изолированно
        $user = new User(1, 'old@mail.com');
        $repo->save($user);
    
        $service = new UserService($repo);
        $service->changeEmail(1, 'new@mail.com');
    
        $updatedUser = $repo->findById(1);
        $this->assertEquals('new@mail.com', $updatedUser->getEmail()); // Проверка состояния
    }
    

Ключевые принципы использования

  • Зависимость через абстракцию: Для подмены зависимость должна быть определена через интерфейс или абстрактный класс (MailerInterface, UserRepositoryInterface). Это позволяет внедрить фейк через Dependency Injection (DI).
  • Состояние vs. Поведение: Выбирайте тип фейка осознанно. Часто предпочтительнее тестировать состояние системы (как изменились данные) с помощью фейков или стабов, а не взаимодействие (что и сколько раз вызывалось) с помощью моков. Избыточное мокирование ведёт к хрупким тестам, которые ломаются при любом рефакторинге.
  • Скорость и надёжность: Правильно написанные фейки делают тестовую базу быстрой (тысячи тестов за секунды) и стабильной (не зависят от внешнего окружения).

Заключение

Фейки — это не просто "обманки", а фундаментальный архитектурный инструмент для создания эффективных, изолированных и поддерживаемых модульных тестов. Их грамотное применение (понимание различий между стабами, моками, спаями и фейками) напрямую влияет на качество тестового покрытия и, как следствие, на надёжность и гибкость кодовой базы. В PHP-экосистеме для работы с ними существуют мощные инструменты, интегрированные в основные фреймворки для тестирования.