Урок 39: Тестирование кода на PHP (Unit-тесты с PHPUnit)

На предпоследнем уроке мы поговорим о важной теме, которая часто вызывает у новичков смешанные чувства о тестировании кода. В частности, мы разберем, что такое unit-тестирование, как использовать PHPUnit и почему это важно для любого разработчика.

Unit-тестирование это метод проверки отдельных частей кода, например функций или методов на корректность работы. Представьте, что вы написали функцию для сложения двух чисел. Unit-тест запустит эту функцию с разными входными данными (например, 2+3, -5+10) и убедится, что результат всегда верный.

Зачем нужно Unit-тестирование?

  1. Раннее обнаружение ошибок. Тесты помогают выявить баги до того, как код попадет в продакшен.
  2. Упрощение рефакторинга. Если вы решите изменить реализацию функции, тесты покажут, не сломали ли вы что-то.
  3. Документирование кода. Тесты показывают, как должна работать функция, что особенно полезно для новых разработчиков.

Основные термины

  • Тест-кейс (Test Case). Набор условий для проверки работы кода.
  • Assertion (Утверждение). Проверка, что результат соответствует ожидаемому (например, assertEquals(5, $result)).
  • Фикстура (Fixture). Настройка окружения для тестов (например, подключение к базе данных).

Установка PHPUnit

PHPUnit это популярный фреймворк для unit-тестирования в PHP. Установим его через Composer:

bash
composer require --dev phpunit/phpunit

Создайте файл phpunit.xml в корне проекта для настройки:

xml
<phpunit bootstrap="vendor/autoload.php">
    <testsuites>
        <testsuite name="My Tests">
            <directory>tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

Пишем первый тест

Предположим, у нас есть класс Calculator в файле src/Calculator.php:

class Calculator {
    public function add($a, $b) {
        return $a + $b;
    }
}

Создадим тест в tests/CalculatorTest.php:

use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase {
    public function testAdd() {
        $calculator = new Calculator();
        $this->assertEquals(5, $calculator->add(2, 3)); // Проверяем, что 2+3=5
    }
}

Запустим тест:

bash
./vendor/bin/phpunit

Если все верно, вы увидите сообщение: OK (1 test, 1 assertion).

Аннотации и методы PHPUnit

Аннотации

  • @test: Помечает метод как тестовый (альтернатива имени метода с префиксом test).
/**
 * @test
 */
public function addTwoNumbers() {
    // ...
}
  • @dataProvider: Позволяет использовать провайдер данных для теста.

Методы жизненного цикла

  • setUp(): Выполняется перед каждым тестом (например, инициализация объекта).
  • tearDown(): Выполняется после каждого теста (например, очистка ресурсов).

Пример:

class CalculatorTest extends TestCase {
    private $calculator;

    protected function setUp(): void {
        $this->calculator = new Calculator();
    }

    public function testAdd() {
        $this->assertEquals(5, $this->calculator->add(2, 3));
    }
}

Практические примеры

Пример 1: Тестирование функции проверки палиндрома

Код функции (src/Palindrome.php):

class Palindrome {
    public function isPalindrome($str) {
        $str = strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $str));
        return $str == strrev($str);
    }
}

Тест (tests/PalindromeTest.php):

use PHPUnit\Framework\TestCase;

class PalindromeTest extends TestCase {
    public function testIsPalindrome() {
        $palindrome = new Palindrome();

        $this->assertTrue($palindrome->isPalindrome('A man, a plan, a canal: Panama'));
        $this->assertFalse($palindrome->isPalindrome('Hello World'));
        $this->assertTrue($palindrome->isPalindrome(''));
    }
}

Пример 2: Использование Data Provider

Добавим в PalindromeTest:

public function palindromeProvider() {
    return [
        ['A man a plan a canal Panama', true],
        ['PHP', false],
        ['Madam', true],
    ];
}

/**
 * @dataProvider palindromeProvider
 */
public function testIsPalindromeWithProvider($input, $expected) {
    $palindrome = new Palindrome();
    $this->assertEquals($expected, $palindrome->isPalindrome($input));
}

Тестирование исключений

Предположим, ваш метод должен выбрасывать исключение при неверных данных:

class User {
    public function setAge($age) {
        if ($age < 0) {
            throw new InvalidArgumentException("Возраст не может быть отрицательным");
        }
        $this->age = $age;
    }
}

Тест:

public function testSetAgeThrowsException() {
    $user = new User();
    $this->expectException(InvalidArgumentException::class);
    $user->setAge(-5);
}

Практические задачи

  1. Задача 1. Напишите тесты для класса EmailValidator, который проверяет корректность email-адреса.
  2. Задача 2. Создайте метод divide($a, $b) в классе Calculator, который возвращает результат деления. Напишите тест, проверяющий, что деление на ноль вызывает исключение.
  3. Задача 3. Используя Data Provider, протестируйте функцию isEven($number), которая определяет, является ли число четным.

Тестирование это неотъемлемая часть профессиональной разработки. Начните с простых тестов и постепенно вы научитесь покрывать тестами даже сложные сценарии. Хорошие тесты экономят часы отладки.

Если вы хотите глубже погрузиться в PHP, не забудьте посмотреть полный курс по PHP для начинающих. Там вас ждут еще больше примеров, проектов и секретов мастерства.