Изученной информации уже достаточно для тестирования в повседневной практике разработки. Далее мы будем погружаться в более сложные темы и возможности PHPUnit, но для начала пройдем полный путь тестирования библиотеки. В этом уроке мы поговорим об организации тестов, хороших и плохих практиках. Это поможет сформировать правильное отношение к тестированию в целом.
В этом уроке мы разберем основы модульного тестирования. Это тестирование направлено на проверку модулей программы в изоляции от всех остальных частей. Эти тесты обычно проверяют базовые конструкции языка — функции, модули и классы. Такие тесты не гарантируют работу всего приложения, но зато помогают проверить его отдельные модули. Это особенно полезно в работе с модулями со сложной логикой.
Попробуем протестировать стек. Напомним, что стек — это список элементов, организованных по принципу LIFO. Данные добавляются в стек в прямом порядке, а извлекаются — в обратном. Как правило, сам стек используется для реализации алгоритмов. Его особенно часто можно встретить внутри языков программирования, в операционных системах и другом низкоуровневом коде:
<?php
// Вымышленная библиотека
use Stack;
$stack = Stack\make();
Stack\isEmpty($stack); // true
Stack\push($stack, 1); // (1)
Stack\push($stack, 2); // (2, 1)
Stack\push($stack, 3); // (3, 2, 1)
Stack\isEmpty($stack); // false
Stack\pop($stack); // 3. В стеке (2, 1)
Stack\pop($stack); // 2. В стеке (1)
Stack\pop($stack); // 1. В стеке пусто
Stack\isEmpty($stack); // true
Сначала решим организационные вопросы. Если предположить, что реализация стека лежит в файле src/Stack.php, то его тест мы положим в файл tests/StackTest.php.
Тестируем основную функциональность
Теперь напишем первый тест. Первый тест всегда должен проверять позитивный сценарий — тот, в котором задействована основная функциональность тестируемого компонента:
<?php
namespace Hexlet\Phpunit\Tests;
use PHPUnit\Framework\TestCase;
use Hexlet\Phpunit\Stack;
class StackTest extends TestCase
{
public function testMainFlow(): void
{
$stack = Stack\make();
// Добавляем два элемента в стек и затем извлекаем их
Stack\push($stack, 'one');
Stack\push($stack, 'two');
$value1 = Stack\pop($stack);
$this->assertEquals('two', $value1);
$value2 = Stack\pop($stack);
$this->assertEquals('one', $value2);
}
}
Этот тест проверяет, что два основных метода работают правильно без учета пограничных случаев. Для этого внутри теста проверяются два утверждения, которые по очереди проверяют извлекаемые значения из стека.
Тестируем дополнительную функциональность
Следующим тестом будет тест на дополнительные функции стека. К таким у нас относится функция isEmpty()
, которая проверяет, пустой ли стек:
<?php
namespace Hexlet\Phpunit\Tests
use PHPUnit\Framework\TestCase;
use Hexlet\Phpunit\Stack;
class StackTest extends TestCase
{
public function testIsEmpty(): void
{
$stack = Stack\make();
$this->assertTrue(Stack\isEmpty($stack));
Stack\push($stack, 'one');
$this->assertFalse(Stack\isEmpty($stack));
Stack\pop($stack);
$this->assertTrue(Stack\isEmpty($stack));
}
}
В этом тесте проверяются сразу три ситуации:
- Начальное состояние стека
- Состояние стека после добавления элементов
- Состояние стека после извлечения всех элементов
В принципе, этого достаточно. Хотя в теории возможны ситуации, при которых isEmpty()
все равно сломается. Нужно ли пытаться найти все варианты? Нет, каждая написанная строчка кода в проекте — это трата ресурсов и потенциальное место для изменения в случае правок. Если есть сомнения, нужно ли писать проверку или нет, то лучше не пишите. Со временем вы поймете, сколько тестов нужно написать и в какой момент будет правильнее перестать писать их. Редкие ситуации требуют покрытия тестами только тогда, когда они критичны для работоспособности.
Пограничные случаи
Последнее, что можно протестировать — это поведение функции pop()
, когда в стеке нет ни одного элемента. По задумке, стек выбрасывает исключение, если мы пытаемся взять элемент из пустого стека. Эта ситуация рассматривается как ошибочная, поэтому программист всегда должен убеждаться, что стек не пустой. Для отлова исключений в PHPUnit используется специальное утверждение expectException():
<?php
namespace Hexlet\Phpunit\Tests
use PHPUnit\Framework\TestCase;
use Hexlet\Phpunit\Stack;
class StackTest extends TestCase
{
public function testPop(): void
{
// Ожидание ставится до вызова кода
$this->expectException(Exception::class);
$stack = Stack\make();
Stack\pop($stack); // Boom!
}
}
https://replit.com/@hexlet/php-testing-unit-tests-stack#tests/StackTest.php
Не всегда пограничные случаи так легко увидеть. Маловероятно, что каждый программист сможет сразу написать все нужные тесты. Если в коде возникла ошибка, для которой не было теста, нужно поступить так: сначала написать тест, который воспроизводит эту ошибку, и только потом — исправлять ее. Только так можно поддерживать достаточный уровень надежности, не превращая разработку в непрерывную починку багов.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.