Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124
Physical Address
304 North Cardinal St.
Dorchester Center, MA 02124


Поговоримо в цій статті про PHP Unit Testing. Коли я тільки починав писати PHP-код, тестування здавалося чимось надто складним і зайвим. Навіщо витрачати час на написання тестів, якщо можна просто перевірити функціонал у браузері? Але після першого серйозного багу на продакшені, який міг би виявити звичайний юніт-тест, я зрозумів — автоматизоване тестування не розкіш, а необхідність.
У цьому гайді розповім, як почати писати юніт-тести на PHP за допомогою PHPUnit — найпопулярнішого фреймворку для тестування. Обіцяю: без занудної теорії, тільки практика та реальні приклади.
Юніт-тестування — це процес перевірки окремих компонентів вашого коду (функцій, методів, класів) на правильність роботи. Уявіть, що ви будуєте будинок: перед тим як монтувати всю конструкцію, ви перевіряєте кожну цеглину окремо. Так само працюють юніт-тести — вони гарантують, що кожен маленький шматочок вашого додатку працює так, як очікується.
Основні переваги юніт-тестування:
Але є й мінуси, про які чесно треба сказати:
PHPUnit — це стандарт де-факто для тестування PHP-додатків. Встановити його можна кількома способами, але я рекомендую через Composer — це найпростіший і найнадійніший варіант.
Відкрийте термінал у кореневій директорії вашого проєкту і виконайте:
composer require --dev phpunit/phpunitCode language: JavaScript (javascript)Прапорець --dev каже Composer, що PHPUnit потрібен тільки для розробки, а не для продакшену. Після встановлення перевірте версію:
./vendor/bin/phpunit --versionЯкщо побачили щось на кшталт “PHPUnit 10.x.x”, вітаю — все працює! Тепер створіть файл конфігурації phpunit.xml у корені проєкту. Це необов’язково, але дуже зручно:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
colors="true"
verbose="true">
<testsuites>
<testsuite name="Application Test Suite">
<directory>./tests</directory>
</testsuite>
</testsuites>
</phpunit>Code language: HTML, XML (xml)Цей конфіг каже PHPUnit шукати тести в папці tests і підключати автолоадер Composer.
Теорія — це добре, але давайте нарешті напишемо щось реальне. Припустимо, у нас є простий клас калькулятора:
<?php
// src/Calculator.php
namespace App;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function divide(float $a, float $b): float
{
if ($b === 0.0) {
throw new \InvalidArgumentException('Ділення на нуль неможливе');
}
return $a / $b;
}
}Code language: HTML, XML (xml)Тепер створимо тест для цього класу. В PHPUnit файли тестів мають закінчуватися на Test.php, а класи — наслідувати PHPUnit\Framework\TestCase:
<?php
// tests/CalculatorTest.php
namespace Tests;
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
private Calculator $calculator;
// Цей метод виконується перед кожним тестом
protected function setUp(): void
{
$this->calculator = new Calculator();
}
public function testAddReturnsCorrectSum(): void
{
// Arrange (підготовка)
$a = 5;
$b = 3;
// Act (дія)
$result = $this->calculator->add($a, $b);
// Assert (перевірка)
$this->assertEquals(8, $result);
}
public function testDivideReturnsCorrectResult(): void
{
$result = $this->calculator->divide(10, 2);
$this->assertEquals(5, $result);
}
public function testDivideByZeroThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->calculator->divide(10, 0);
}
}Code language: HTML, XML (xml)Звернули увагу на структуру Arrange-Act-Assert? Це класичний патерн написання тестів: спочатку готуємо дані, потім виконуємо дію, і нарешті перевіряємо результат. Запустіть тести:
./vendor/bin/phpunitЯкщо побачили зелені галочки — все працює ідеально!


Assertions (твердження) — це серце юніт-тестів. Вони перевіряють, чи відповідає реальний результат очікуваному. PHPUnit пропонує десятки різних assertions, але ось найкорисніші для початку:
assertEquals($expected, $actual) — перевіряє рівність значеньassertSame($expected, $actual) — перевіряє ідентичність (включно з типом)assertTrue($condition) та assertFalse($condition) — булеві перевіркиassertNull($value) та assertNotNull($value) — перевірка на nullassertEmpty($value) — перевіряє, що значення порожнєassertCount($expectedCount, $array) — перевіряє розмір масивуassertStringContainsString($needle, $haystack) — перевіряє наявність підрядкаПриклад практичного використання:
public function testUserDataValidation(): void
{
$user = [
'name' => 'John Doe',
'email' => 'john@example.com',
'age' => 25
];
$this->assertIsArray($user);
$this->assertCount(3, $user);
$this->assertArrayHasKey('email', $user);
$this->assertStringContainsString('@', $user['email']);
$this->assertGreaterThan(18, $user['age']);
}Code language: PHP (php)Часто потрібно протестувати одну функцію з різними вхідними даними. Замість копіювання коду можна використовувати Data Providers — це геніальна фіча PHPUnit:
class CalculatorTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAddWithMultipleInputs(int $a, int $b, int $expected): void
{
$calculator = new Calculator();
$this->assertEquals($expected, $calculator->add($a, $b));
}
public static function additionProvider(): array
{
return [
'positive numbers' => [2, 3, 5],
'negative numbers' => [-5, -3, -8],
'mixed numbers' => [-10, 15, 5],
'with zero' => [0, 7, 7],
];
}
}Code language: PHP (php)Тепер один тест запуститься чотири рази з різними даними. Якщо якийсь сценарій провалиться, ви одразу побачите який саме — завдяки іменованим ключам масиву.
Реальні додатки рідко існують у вакуумі — вони взаємодіють з базами даних, API, файловою системою. Але під час юніт-тестування ми не хочемо чіпати реальну базу чи слати справжні HTTP-запити. Тут на допомогу приходять моки (mocks) — підробки об’єктів.
Припустимо, у нас є сервіс, що відправляє email:
<?php
namespace App;
class UserService
{
public function __construct(
private MailerInterface $mailer
) {}
public function registerUser(string $email): bool
{
// Якась логіка реєстрації...
$this->mailer->send($email, 'Ласкаво просимо!');
return true;
}
}Code language: HTML, XML (xml)Тест із моком виглядатиме так:
public function testRegisterUserSendsEmail(): void
{
// Створюємо мок об'єкта mailer
$mailerMock = $this->createMock(MailerInterface::class);
// Очікуємо, що метод send буде викликаний рівно один раз
$mailerMock->expects($this->once())
->method('send')
->with(
$this->equalTo('test@example.com'),
$this->stringContains('Ласкаво')
);
$userService = new UserService($mailerMock);
$result = $userService->registerUser('test@example.com');
$this->assertTrue($result);
}Code language: PHP (php)Моки дозволяють перевіряти не тільки результат, а й те, як ваш код взаємодіє з іншими компонентами. Це надзвичайно корисно для тестування бізнес-логіки.
За роки роботи я бачив безліч типових помилок у тестах. Ось топ-5, які варто уникати:
Не потрібно тестувати вбудовані PHP-функції або код сторонніх бібліотек. Вони вже протестовані:
// Погано - тестуємо вбудовану функцію PHP
public function testArrayMerge(): void
{
$result = array_merge([1, 2], [3, 4]);
$this->assertEquals([1, 2, 3, 4], $result);
}Code language: PHP (php)Один тест = одна перевірка. Якщо тест перевіряє багато речей, його важко підтримувати:
// Погано - занадто багато assertions в одному тесті
public function testUserCreation(): void
{
$user = new User('John', 'john@test.com');
$this->assertEquals('John', $user->getName());
$this->assertNotEmpty($user->getId());
$this->assertTrue($user->isActive());
$this->assertInstanceOf(DateTime::class, $user->getCreatedAt());
// ... ще 10 перевірок
}Code language: PHP (php)Кожен тест має бути незалежним. Ніколи не покладайтеся на порядок виконання тестів або на стан після попереднього тесту.
Якщо ви працюєте з Laravel чи іншим фреймворком, там зазвичай вже є готова інтеграція з PHPUnit. У Laravel, наприклад, можна тестувати HTTP-запити надзвичайно просто:
public function testHomePage(): void
{
$response = $this->get('/');
$response->assertStatus(200);
$response->assertSee('Ласкаво просимо');
}Code language: PHP (php)Детальніше про тестування в Laravel читайте в нашому гайді по Laravel Eloquent.
Щоб зробити роботу з тестами ще зручнішою, рекомендую:
./vendor/bin/phpunit --coverage-html coverageДля налаштування CI/CD процесів рекомендую ознайомитись з GitHub Actions для Laravel.
Юніт-тестування — це не магія і не ракетна наука. Це просто навичка, яку можна вивчити за кілька днів практики. Почніть з малого: напишіть тести для однієї простої функції. Потім для класу. Потім для модуля. Дуже швидко ви відчуєте, як тести дають впевненість у вашому коді.
Пам’ятайте: хороший тест — це тест, який легко читати і розуміти. Якщо через місяць ви не зможете зрозуміти свій власний тест, значить він написаний погано. Пишіть тести так, ніби їх читатиме людина, яка нічого не знає про ваш проєкт.
Офіційна документація PHPUnit доступна на phpunit.de, а детальніше про можливості фреймворку можна дізнатись у документації PHPUnit.