Перейти к содержимому
Поиск
Php Разработка

PHP: полный мануал с примерами, функциями и ООП

Практический мануал PHP: от переменных и читаемого кода до безопасных форм, интеграций, базы данных и ООП на примере магазина. Учебный маршрут, поиск функций, подробные примеры, разбор ошибок и исходники для скачивания.

Александр НекрасовИзменено:
Практический мануал PHP: от переменных и функций к объектной модели интернет-магазина
PHP 8.5.11149 глав4458 карточек справочника
Дерево руководства

Глава 61 · Практика

Тестирование: успешный путь, границы и отказ инфраструктуры

Проверяем правила, формы, SQL и интеграции так, чтобы тесты обнаруживали реальные ошибки.

Первый тест: сравниваем полученное с тем, что ожидали

После изменения расчёта хочется знать, не сломалась ли старая корзина. Тест задаёт вход, вызывает код и сравнивает результат с ожиданием. Он полезен даже до знакомства с тестовым фреймворком.

Что уже пригодится. переменная и присваивание, массив, объявление и вызов функции, исключения

Три части проверки
  1. Дано: цена 150 и количество 2
  2. Действие: вызвать total(150, 2)
  3. Проверка: результат строго равен 300

!== проверяет различие значения или типа. При ошибке тест должен сообщить о ней, а не просто напечатать число для ручного просмотра. Ожидаемый результат определяют по задаче, не копируют из ошибочного вывода программы.

Самостоятельная проверка расчётаСамостоятельный пример
<?php
declare(strict_types=1);

function total(int $price, int $quantity): int
{
    return $price * $quantity;
}
$actual = total(150, 2);
if ($actual !== 300) {
    throw new RuntimeException('Ожидалось 300');
}
echo "Проверка пройдена\n";
Ожидаемый результат
Проверка пройдена

Измените * на + и запустите снова: исключение сделает ошибку видимой. Затем верните умножение. Следующие полезные случаи — нулевое количество и недопустимый вход; сначала определите, что программа обязана делать в каждом случае.

Следующая ступень. Ниже переносим проверки в PHPUnit, изолируем внешние сервисы и различаем модульные, интеграционные и HTTP-тесты.

Что именно должен доказать тест

Наглядная модель

Одна задача проверяется на разных уровнях

Правило обещает бесплатную доставку от 5000 рублей. Отдельно проверим правило и реальное сохранение заказа.

  • Правило без сети и БД

    Быстро проверяем значения на границе условия.

    • 4999 рублей

      Бесплатная доставка ещё не положена. Ожидаемый результат берём из правила.

    • 5000 и 5001 рубль

      Доставка бесплатна на пороге и выше него. Так обнаруживается неверный знак сравнения.

    • Недопустимая сумма

      Отрицательная сумма должна быть отклонена, а не обработана как обычный заказ.

  • Сценарий с настоящей тестовой БД

    Проверяем взаимодействие и сохранённые факты.

    • Создать заказ

      Проверяем запись и позиции в изолированной базе, а не только текст «успех».

    • Повторить тот же ключ

      Убеждаемся, что не возник второй заказ и вернулся согласованный результат повтора.

    • Сломать сохранение

      Проверяем откат: остаток и заказ не должны остаться в противоречивом состоянии.

Unit, интеграционный, HTTP- и браузерный тесты дополняют друг друга. Успех проверки функции не доказывает удобство формы.

Тест нужен не для зелёной галочки, а для проверки обещания. Если правило говорит «бесплатная доставка от 5000 рублей», тесты должны проверить сумму чуть ниже порога, на пороге и выше, а также самовывоз и недопустимую сумму. Если тест повторяет реализацию тем же выражением, он способен подтвердить ту же ошибку. Ожидаемый результат берут из правила, а не вычисляют копией алгоритма.

Модульный тест проверяет небольшое правило без сети и базы. Интеграционный — взаимодействие с реальной базой, файловой системой или адаптером. HTTP-тест проверяет запрос, cookies, редирект и тело ответа. Сквозной браузерный — поведение человека, включая фокус, клавиатуру и мобильную навигацию. Эти уровни дополняют друг друга; один успешный unit-тест не доказывает, что форма правильно отправляет CSRF.

Каждый тест готовит собственные данные и убирает изменения либо использует изолированное хранилище. Никогда не направляйте автоматические тесты на production-базу или реальные платёжные ключи. Тестовая отправка почты должна попадать в локальный транспорт или fake. Отдельно проверяется, что production-конфигурация не подхватилась случайно.

Таблица случаев делает границы видимыми

Проверка правила без стороннего фреймворкаСамостоятельный пример
<?php
declare(strict_types=1);

function shipping(int $subtotal): int {
    if ($subtotal < 0) throw new InvalidArgumentException('subtotal');
    return $subtotal >= 500000 ? 0 : 39000;
}
$cases = [[0,39000],[499999,39000],[500000,0],[500001,0]];
foreach ($cases as [$input,$expected]) {
    if (shipping($input) !== $expected) throw new RuntimeException('Wrong shipping');
}
try {
    shipping(-1);
    throw new RuntimeException('Expected rejection');
} catch (InvalidArgumentException) {}
echo count($cases)." values + negative case passed
";
Ожидаемый результат
4 values + negative case passed

Проверки не используют assert, который может быть отключён настройкой PHP. В настоящем проекте PHPUnit даёт удобные assertions, отчёты и data providers; смысл границ остаётся тем же.

Подменяйте внешнюю границу, а не смысл задачи

Повтор уведомления с тестовым транспортомСамостоятельный пример
<?php
declare(strict_types=1);

interface Notifier { public function send(string $message): void; }
final class RecordingNotifier implements Notifier {
    public array $messages = [];
    public function send(string $message): void { $this->messages[] = $message; }
}
final class WelcomeService {
    public function __construct(private Notifier $notifier) {}
    public function welcome(string $name): void {
        if (trim($name) === '') throw new InvalidArgumentException('name');
        $this->notifier->send('Welcome, '.$name);
    }
}
$notifier = new RecordingNotifier();
$service = new WelcomeService($notifier);
$service->welcome('Mira');
try { $service->welcome(' '); } catch (InvalidArgumentException) {}
if ($notifier->messages !== ['Welcome, Mira']) throw new RuntimeException('Unexpected sends');
echo "one valid notification
";
Ожидаемый результат
one valid notification

Fake доказывает, что неверное имя не отправляет уведомление. Он не доказывает доставку SMTP: её проверяют отдельным интеграционным тестом транспорта. Массив messages здесь намеренно открыт для наблюдения в тестовом объекте.

Часы, случайность и ожидание тоже зависимости. Передав FixedClock или fake wait, можно проверить истечение срока и retry без сна. Это ускоряет тесты и делает их воспроизводимыми. При этом не нужно подменять каждую внутреннюю функцию: чрезмерные моки привязывают тест к порядку вызовов и мешают безопасному рефакторингу.

Для API сохраняют обезличенные примеры ответов и проверяют успешный JSON, повреждённый JSON, неправильную схему, 429, 503 и таймаут. Контрактный тест периодически сравнивает предположения с sandbox провайдера. Нельзя считать сохранённый fixture вечной истиной: контракт меняется. Дата и версия примера помогают понимать его происхождение.

Что обязательно проверить у формы и базы

Форма требует сценариев отсутствующего поля, массива вместо строки, слишком длинного значения, неверного CSRF, чужого объекта и повторного POST. Проверяйте статус, ошибки возле полей, отсутствие побочного эффекта и корректный редирект после успеха. HTML можно разобрать DOM-парсером; не проверяйте целую страницу одной хрупкой строкой, если важны всего два атрибута.

Для транзакции специально вызовите ошибку последней записи и убедитесь, что первая откатилась. Для уникального ключа запустите конкурирующие операции на целевой СУБД. SQLite in-memory хорошо проверяет простые запросы, но не имитирует все уровни изоляции PostgreSQL или MySQL. Проверка синтаксиса php -l вообще не исполняет запросы и должна называться именно проверкой синтаксиса.

Браузерный тест должен нажать настоящую кнопку, проверить фокус после ошибки, сохранить введённые значения и пройти форму на узком экране. Удобство нельзя вывести из отсутствия PHP-ошибок. Для доступности полезны клавиатура, понятные подписи и отсутствие горизонтального переполнения; автоматический анализатор дополняется визуальным просмотром.

Статический анализ и отчёты

PHPStan или Psalm обнаруживают несовместимые типы и неверные предположения ещё до выполнения. Форматтер поддерживает единый стиль. PHPUnit запускает поведенческие тесты. Coverage показывает, какие строки исполнялись, но не доказывает качество проверок: можно пройти весь код без meaningful assertions. Сначала тестируйте критические деньги, доступ, записи и отказные ветки.

В CI зафиксируйте версии PHP и расширений, команды установки и тестов. При заявленной поддержке нескольких версий используйте матрицу. Ошибку теста сохраняйте с воспроизводимым входом, seed и безопасным журналом. Не расширяйте timeout просто потому, что тест нестабилен: сначала выясните, не зависит ли он от внешней сети, реальных часов или общего состояния.

Регрессия, проверка побочного эффекта и читаемость

Когда найден баг, сначала воспроизведите его минимальным тестом с понятным названием: «повтор одного ключа не создаёт второй заказ». Затем исправляйте код. Такой тест сохраняет причину решения, а не только текущую форму метода. В комментарии полезно объяснить необычную границу или ссылку на контракт; пересказывать каждую строку подготовки не нужно.

Проверяйте не только возвращённый текст. При отказе формы число заказов должно остаться прежним, уведомление не должно отправиться, транзакция не должна зависнуть. При успешном повторе идемпотентного запроса должен вернуться прежний ID. Именно побочные эффекты определяют надёжность сервиса, даже если HTTP-ответ выглядит одинаково.

Фикстуры должны быть короткими и выражать смысл: товар с нулевым остатком, заказ другого пользователя, просроченный токен. Большой случайный дамп усложняет понимание падения. Общая фабрика данных допустима, но важные для сценария поля задавайте прямо в тесте. Тогда читатель понимает, почему ожидается конкретный результат, не открывая пять вспомогательных файлов.

Практика: проверьте решение, а не только успешный путь

Тест retry ждёт настоящие 1, 2 и 4 секунды и иногда падает в CI. Как сохранить смысл и убрать нестабильность?

Разбор решения

Передать fake wait, записывающий интервалы, и fake transport с заранее заданными ответами. Проверить число попыток, интервалы, общий бюджет и остановку на неповторяемом статусе. Один отдельный интеграционный тест может проверить адаптер ожидания, но весь набор не должен реально спать.

Источники и обсуждения

Код и руководство с собой

Скачайте самостоятельные примеры с исходными данными, инструкциями и ожидаемыми результатами. Примеры HTTP и фрагменты для проекта помечены отдельно: им нужно указанное окружение.

Код не исполняется на сайте. Скачайте нужный пример и запускайте его в своём локальном окружении.

Материалы сверены 2026-10-12. Примеры и объяснения написаны для этого руководства; источники и обсуждения доступны в каждой главе.