lvl1
Отличный выбор — это как раз то, что как минимум на уровне middle обязательно нужно знать, особенно для PHP-разработчика.
Давай сделаю тебе так, чтобы ты получил структурную карту + практическую пользу:
- Что такое юнит-тесты, что тестировать, что нет
- Как работает PHPUnit
- Как пишется тест
- Как подменять зависимости (mock / stub)
- Как мыслить "тестируемо"
- Что тебя могут спросить на собеседовании
🧩 Основы тестирования и PHPUnit
1. Что такое юнит-тесты
Unit-тесты проверяют работу одной конкретной части кода (функции, метода), изолированно от остальной системы.
Цель:
- Поймать баги сразу при разработке
- Не бояться рефакторинга
- Упростить проверку бизнес-логики
📌 Ключевая идея: маленький, быстрый, независимый тест
2. Что именно тестировать
✅ Тестируем:
- Бизнес-логику (например, расчёт скидки)
- Поведение методов класса (например, создание сущности)
- Граничные случаи (пустой ввод, невалидные данные)
- Побочные эффекты (вызов другого метода, логирование)
❌ Не тестируем в unit-тестах:
- Сторонние библиотеки (они сами себя тестируют)
- Сложный IO: сеть, база данных (это интеграционные тесты)
- Статику фронтенда (это отдельный тип тестирования)
3. Как работает PHPUnit
- PHPUnit запускается в консоли
- Загружает тестовые классы
- Выполняет методы, которые начинаются с
test - Проверяет, проходят ли assert-выражения
Устанавливается через composer:
composer require --dev phpunit/phpunit
Запуск:
./vendor/bin/phpunit
4. Пример базового теста
Допустим, у нас есть класс:
class Calculator { public function add(int $a, int $b): int { return $a + $b; } }
Тест:
use PHPUnit\Framework\TestCase; class CalculatorTest extends TestCase { public function testAddReturnsSum(): void { $calc = new Calculator(); $result = $calc->add(2, 3); $this->assertEquals(5, $result); } }
Запускаем, и PHPUnit покажет зелёный тест ✅
5. Моки и стабы (подмена зависимостей)
Когда метод зависит от внешнего сервиса (например, Logger или Repository), мы не хотим в юнит-тесте использовать настоящую реализацию.
Вместо этого — мокаем:
$loggerMock = $this->createMock(Logger::class); $loggerMock->expects($this->once()) ->method('info') ->with('Order created'); $orderService = new OrderService($loggerMock); $orderService->createOrder();
createMock()— создаёт поддельный объект.expects($this->once())— проверяем, что метод вызван ровно один раз.with('Order created')— проверяем аргумент вызова.
6. Как мыслить тестируемо
Когда пишешь код — заранее думай:
- Есть ли у метода внешние зависимости? → передавай их через конструктор.
- Метод слишком сложный? → разбей на части.
- Используй интерфейсы для зависимостей → можно будет замокать.
Если подходить к коду так, тесты писать не больно.
7. Типичные вопросы на собеседовании
— Что такое юнит-тесты и как они отличаются от интеграционных?
→ Unit-тесты проверяют отдельные методы и классы изолированно, интеграционные — несколько компонентов вместе, включая, например, базу данных.
— Что такое mock, stub, spy?
→ Mock проверяет, что метод был вызван с нужными параметрами. Stub возвращает предопределённое значение. Spy следит за вызовами.
— Какую часть проекта вы покрываете тестами?
→ Бизнес-логику, пограничные случаи, валидацию, работу с внешними сервисами через мок.
— Как организуете структуру тестов?
→ Обычно: папкаtests/, классы с суффиксомTest, методы начинаются сtest.
📦 Мини-чеклист для реальной практики
- ✅ Стартовый класс теста — расширяет
TestCase - ✅ Методы начинаются с
test - ✅ Зависимости мокаем через
createMock - ✅ Для каждого метода класса — отдельный метод теста
- ✅ Проверяем позитивные и негативные сценарии
- ✅ В CI — автоматический запуск тестов
Хочешь — могу дать практическое задание:
Например, взять твой TaskService и написать к нему тест: с мокнутым репозиторием и проверкой вызова save().
Или пойдём к следующей теме: JavaScript (без фреймворков, современный JS).
Твой выбор 🚀