Руководство по модульному тестированию. Часть I. Введение в PHPUnit

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

Я слегка коснусь темы разработки через тестирование, но особо заострять на этом внимание не буду, поскольку считаю, что написание тестопригодного кода и изучение того, как проводить тестирование, это и так большой объем новой информации, и рассказывать еще об абсолютно другом методе разработки это уже слишком. Мы будем двигаться маленькими шажками. Чего в этой серии статей НЕ будет, так этого того, почему вы должны проводить тестирование, почему тестирование это хорошо, и какие еще существуют плюсы тестирования, помимо легкого «ретуширования».

Если вы хотите почитать о преимуществах тестирования, я бы порекомендовал вам книгу Себастьяна Бергмана (Sebastian Bergman) Реальные решения для разработки качественных PHP-фреймворков и приложений (Real-World Solutions for Developing High-Quality PHP Frameworks and Applications), а также Блог Ворчливого Программиста, который ведет мой приятель Крис Хартьес (Chris Hartjes, The Grumpy Programmer’s blog). Крис уже не первый год буквально кричит PHP-сообществу о том, что они должны проводить тестирование. Также он проводит обучающие занятия, и я настоятельно рекомендую его вам.

Перед тем как приступать

Занимаясь написанием этой серии статей, я исходил из того, что у читателя уже настроена соответствующая среда разработки. Я настоятельно рекомендую вам использовать виртуальную машину (ВМ), которая имитурует среду сервера, а не запускать сервер одновременно с работающей операционной системой.

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

Установка фреймворка для тестирования PHPUNIT

Раньше рекомендовалось устанавливать PHPUnit через PEAR. Но теперь, когда лидером среди диспетчеров пакетов стал Composer, то предлагаю использовать именно его.

Если вы не знаете, что такое Composer или как его использовать, почитайте мою статью «Пространства имен Composer за 5 минут» (Composer Namespaces in 5 Minutes). В ней речь идет о Composer в целом и о том, как он использует PSR-0 для автозагрузки.

PHPUnit устанавливается с помощью всего одной строки в файле composer.json:

{
    "require-dev": {
        "phpunit/phpunit": "3.7.14"
    }
}

Также следует установить XDebug. Если вы не пользуетесь Xdebug, тогда почитайте «Xdebug и вы: Почему вам следует пользоваться настоящим отладчиком» (Xdebug and You: Why You Should be Using a Real Debugger) и перестаньте, наконец, быть пещерным человеком. Использовать Xdebug намного лучше, чем засорять свою кодовую базу всякими echo, print_r и var_dump. Также необходимо использовать чудесные инструменты отчетности о покрытии кода, которые предлагает PHPUnit.

Чтобы Composer загрузил ваши новые библиотеки, просто выполните команду ./composer.phar update -dev, и Composer покажет вам все чудеса, на которые он способен.

Запуск PHPUNIT

При запуске PHPUnit чаще всего вам придется работать с файлом  ./vendor/bin/phpunit. Он очень простой и лаконичный — он просто ищет автозагрузчика Composer и загружает его. Чтобы запустить PHPUnit, выполните команду ./vendor/bin/phpunit. Вы увидите все доступные вам опции.

Структурирование проекта

Поскольку мы используем Composer, мы не будем жалеть времени на правильную настройку нашего проекта, чтобы потом не возникло проблем с автозагрузчиком. Мы назовем проект phpUnitTutorial и будем использовать это название в качестве пространства имен.

Обновите файл composer.json до следующего вида:

{
    "require": {
    },
    "require-dev": {
        "phpunit/phpunit": "3.7.14"
    },
    "autoload": {
        "psr-0": {
            "phpUnitTutorial": ""
        }
    }
}

Затем обновите Composer при помощи команды ./composer.phar update. Файлы по нашему проекту будут в папке  phpUnitTutorial, которая будет располагаться на том же уровне, что и папка vendors. Просто создайте пустую папку, чтобы ваша структура папок выглядела следующим образом:

composer.json
composer.phar
phpUnitTutorial/
vendor/

Настройка phpunit.xml

Запущенный PHPUnit проверит ваши тесты, используя встроенные стандартные настройки. Вы можете переопределить многие стандартные настройки через командную строку, но есть и более удобный способ: файл конфигурации phpunit.xml. Да, да, этот противный XML!. Я сам вас прекрасно понимаю, но этот файл довольно безобидный.

В корне своего проекта создайте phpunit.xml со следующим содержанием:

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./phpUnitTutorial/Test/</directory>
        </testsuite>
    </testsuites>
</phpunit>

Это очень маленький и простой файл конфигурации, который, однако, задает две важные опции:colors="true" отвечает за то, чтобы результаты тестов были цветными, и<directory>./phpUnitTutorial/Test/</directory> сообщает PHPUnit, где будут располагаться ваши тесты, чтобы вам не надо было сообщать ему это вручную каждый раз, когда вы будете выполнять тесты.

Эту папку вы еще не создавали, поэтому создайте ее сейчас. Структура файлов будет выглядеть следующим образом:

composer.json
composer.phar
phpUnitTutorial/
phpUnitTutorial/Test/
vendor/
phpunit.xml

Все тесты вашего приложения должны располагаться в phpUnitTutorial/Test.

Соглашения

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

Файловая структура и имена файлов

Первое соглашение, которое мы рассмотрим, это файловая структура и имена файлов. Ваши тесты должны отражать ваш код (но при этом они должны находится в своей отдельной папке), а тестовые файлы должны соответствовать файлу, который они тестируют, с добавлением Test. Если бы в нашем примере код выглядел следующим образом:

./phpUnitTutorial/Foo.php
./phpUnitTutorial/Bar.php
./phpUnitTutorial/Controller/Baz.php

то наши тесты имели бы следующую структуру:

./phpUnitTutorial/Test/FooTest.php
./phpUnitTutorial/Test/BarTest.php
./phpUnitTutorial/Test/Controller/BazTest.php

Имена классов

Имена классов это абсолютно то же самое, что имена файлов. Ваш файл должен всегда являться именем вашего класса (что, в принцие, так и есть для вашего не-тестового кода!)

Имена методов (тестирования)

Ваши имена методов тестирования должны начинаться с test, с маленькой буквы. Имена методов должны описывать то, что тестируется, а также должны включать имя тестируемого метода. Здесь не используются короткие, сокращенные имена методов.

Например, если вы тестируете метод под названием verifyAccount(), и в одном модульном тесте вы хотите протестировать, что пароль совпадает, вы должны назвать свой тест следующим образом: testVerifyAccountMatchesPasswordGiven().

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

Методы должны быть общедоступными

PHPUnit не может выполнять защищенные или частные (закрытые) тесты. Тесты должны быть общедоступными. Аналогично, любые методы, создаваемые вами в качестве вспомогательных, также должны быть общедоступными. Мы не занимаеся здесь созданием публичного API, мы просто хотим писать тесты, поэтому не волнуйтесь по поводу видимости.

Расширения в PHPUnit

Само собой разумеется, но все же. Ваши классы должны расширять класс PHPUnit_Framework_TestCase.

Первый тест

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

Создайте новый файл в  ./phpUnitTutorial/Test/StupidTest.php:

<?php
namespace phpUnitTutorial\Test;
class StupidTest extends \PHPUnit_Framework_TestCase
{
    //
}

Ничего особенного – вы просто выполняете класс. Но обратите внимание на то, что тут мы уже следуем трем соглашениям.

Для начала проверим, что что-либо равно «истина» (true). Утверждения (assert) это сильная сторона PHPUnit, и в следующих статьях этой серии мы поговорим об этом подробнее.

Создайте метод testTrueIsTrue. Вы уже сами видите, насколько глупый этот тест, да?

public function testTrueIsTrue()
{
    //
}

Теперь перейдем к самому коду теста. Он действительно очень простой, поэтому не пытайтесь все усложнять:

public function testTrueIsTrue()
{
    $foo = true;
    $this->assertTrue($foo);
}

Эта зелёная строка

Из корня проекта запустите PHPUnit:

./vendor/bin/phpunit

Должна появиться столь желанная зеленая строка:

Вы выполнили один тестовый файл (1 test), с одним утверждением (1 assertion). Поздравляю, вы стали на один шаг ближе к статусу тестировщика!

Заключение

В этом уроке вы научились устанавливать PHPUnit при помощи Composer, задавать некоторые стандартные настройки и даже выполнили свой первый (глупый) тест.

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

Я понимаю, что этот первый шаг кажется очень незначительным, но он доказывает, что тестирование это отнюдь не что-то непостижимое, понятное только людям с научной степенью. Это просто когда ты говоришь коду: «Я ожидаю, что произойдет то-то и то-то», и код сообщает вам, допустили ли вы где-то ошибку.

В следующей статье я расскажу об утверждениях и аннотациях (включая очень эффективную аннотацию dataProvider) , а также подробно объясню, как создать свой первый нетиповой модульный тест! Всем добра!