Phpunit тесты. Тестирование модулей в PHP. Мифы о юнит тестировании

Так как генерация исключения в коде приложения является частым явлением, рассмотрим как это дело можно тестировать с помощью PHPUnit.
В старых статьях на эту тему можно встретить использование метода setExpectedException() , но имейте ввиду, что в новых версиях phpUnit используется метод expectException() для указания типа ожидаемого исключения.

Метод expectException(), а так же директива @expectedException используются в тестах для указания "ожидать такое-то исключение". Тест считается пройденным, если возникло исключение указанного типа.

Пример.
Исключение возникает если в переданном параметре менее 4-х символов:
class User { public function verifyPassword(array $user){ if(strlen($user["password"])<4){ throw new LengthException("Количество символов в пароле (" .$user["password"]. ") не соответствует требованиям."); } // } }
Тестируем:
class UserTest extends TestCase { public function testVerifyPassword() { $obj = new User(); $user["password"] = "123"; $this->expectException(LengthException::class); $obj->verifyPassword($user); } }
данный код можно было бы переписать так:
class UserTest extends TestCase { /** * @expectedException LengthException */ public function testVerifyPassword() { $obj = new User(); $user["password"] = "123"; $obj->verifyPassword($user); } } т.е. использовать директиву @expectedException с указанием нужного исключения.

ВАЖНО! После того, как PHPUnit поймает ожидаемое исключение, выполнение данного тестирующего метода прекратится!
Другими словами - если в одном методе у вас 2 или больше тестов, например:
$this->expectException(LengthException::class); $this->assertEquals(8, $result, "Двойка в третьей степени"); то при выполнении первого теста с исключением, второй будет пропущен (при условии что исключение будет получено). Поэтому нужно размещать тесты исключений в разных методах или:
- в тест-методе оформлять блоки try...catch;
- использовать data provider.

Анализ покрытия кода тестами.

При большом количестве классов можно забыть протестировать какие-то методы или разные варианты возвращаемых ими результатов. Так же можно что-то отложить на потом или вообще вдруг решить тестировать то, что до этого не собирались. Как же оперативно проверить что уже было протестировано, а что нет?!! В PHPUnit для этого используется инструмент php-code-coverage .

Для использования нужно предварительно подключить php-расширение Xdebug в файле интерпретатора php - php.ini.
В OpenServer файл php.ini меняется так:
меню -> Дополнительно -> Конфиграция -> PHP
Откроется файл конфигурации, где необходимо раскомментировать строку (удалить спереди точку с запятой):
;zend_extension="e:/openserver/modules/php/PHP-5.6/ext/php_xdebug.dll"
после внесения изменений не забыть перезагрузить сервер.

Проверить подключено ли расширение, выполнить скрипт с командой:
phpinfo(); или открыть ссылку http://127.0.0.1/openserver/phpinfo.php
должен присутствовать блок «xdebug ».

Выполнение:
phpunit --coverage-html tests\coverage тут указываем, что отчет нужно предоставить в виде статичных html файлов и разместить их в папке coverage, которая появится в каталоге tests.

Открыв файл index.html из папки coverage можно увидеть список классов которые попали в отчет. При клике на названии класса открывается страница со статистикой по данному классу. Пример:

Красной строкой выделен фрагмент упущенный при создании тестов. Т.е., в данном случае, нужно написать тест при котором данный метод будт возвращать значение false.
Если при выполнении команды возникнет ошибка:
No whitelist configured, no code coverage will be generated
то нужно изменить/создать в корне проекта файл phpunit.xml и вставить туда блок с указанием каталога который нужно проверять:
app тут указываем, что нужно проверить покрытие кода тестами в php файлах из каталога app .

Так же используется блок для указания каталогов и файлов не требующих проверки.
Подробнее в документации .

При работе с Composer в командной строке, при включенном php-расширение Xdebug , может появляться сообщение:
You are running composer with xdebug enabled. This has a major impact on runtime performance. See https://getcomposer.org/xdebug
В котором сказано, что включенное расширение xdebug оказывает существенное влияние на производительность (снижает). Как решить эту проблему читайте в моей .

В следующей статье я расскажу как работать с имитирующими объектами (моками), чем завершим серию статей по основам использования PHPUnit.

Всем доброго времени суток! Сегодня я хотел бы поговорить с Вами о том, что такое модульное тестирование в PHP .

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

В общем, конечно, данный подход неплох, однако у него есть существенные недостатки. Так, например, при написании, какого-либо достаточно крупного проекта, код будет постепенно засоряться закомментированными отладочными функциями, типа print или print_r .В случае работы над собственным проектом, код которого никто, ну или почти никто, читать не собирается, он, до некоторой поры, будет оправдан.

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

Наступает такой момент, когда один новый класс, метод, условие или цикл - рушат всю систему. Изменения в одном месте приводят к ошибкам в другом. И вот они уже лезут без конца, как из рога изобилия, и становиться понятно, что так дальше невозможно. А все было бы гораздо лучше, если бы сначала, помимо всего прочего, были бы написаны PHP Unit тесты . Не зря ведь говорит, Мартин Фаулер , что когда бы Вы ни пытались напечатать что-то через print в целях отладки или рефакторинга, лучше напишите это в виде Unit теста .

Итак, с теорией вроде ознакомились, теперь перейдем непосредственно к коду. Здесь необходимо сделать важные замечания, все операции проводятся на ПК под управлением Windows 7 c установленным PHP 7 версии. Дальше будет по пунктам.

2) Загруженный файл перемещаем в папкуC:\bin . В этой же папке создаем файл phpunit.bat , записываем в него следующее содержимое: @php C:\bin\phpunit-6.3.0.phar %*

Учтите, что путь C:\bin должен быть определен в системной переменной PATH , иначе при попытке выполнить в консоли команду phpunit Вы получите ошибку!

3) Откройте консоль и выполните команду phpunit , и если все правильно, то в консоли должна отобразиться справка.

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

// файл StackTest.php, расположен в каталоге C:/Projects/php/tests
// подключаем главный класс TestCase из пространства имен PHPUnit\Framework
use PHPUnit\Framework\TestCase;

// определяем тестируемый класс как наследник класса TestCase
class StackTest extends TestCase
{
// тестируемые функции являются публичными, начинаются со слова test
public function testPushAndPop()
{
$stack = ; // создали массив
// и проверили утверждение assert на то, что число элементов в массиве равно нулю
$this->

$this->assertEquals("foo", array_pop($stack));
$this->assertEquals(0, count($stack));
}
}
?>

Код хорошо комментирован, однако поясню пару моментов. Краеугольным камнем Unit тестирования является утверждение (assertion ). Утверждение - это Ваше предположение об ожидаемом значении, или другими словами Вы утверждаете, что значение переменной, элемента массива, результат выполнения метода и т.д. будет равно такому-то значению. В примере выше, при первоначальном создании массива, ожидаемое значение его длины - 0. Так оно и есть на самом деле в нашем примере.

В данном случае мы используем только одно утверждение assertEquals , хотя в классе TestCase библиотеки PHPUnit их несколько десятков, на все случаи жизни, так сказать.

Так тест мы написали, а что дальше? А дальше его надо запустить. Для этого открываем консоль, переходим в папку с нашим тестом (PHP Unit тесты обычно располагаются в отдельной папке tests ) и запускаем команду phpunit , передав ей в аргументе текущий каталог (обозначается одной точкой).

cd C:/Projects/php/tests && phpunit .

Данная команда автоматически пройдется по всем PHP тестам , которые есть в данном каталоге. По завершении выполнения, она выведет информацию о том, сколько тестов пройдено и, возможно, провалено.

Таким образом, сегодня мы с Вами выяснили что такое Unit тестирование в PHP , что применять его не только полезно, но и нужно. А если Вы знаете PHP плохо, или не знаете его совсем, то специально для Вас у меня есть отличный видеокурс , в котором, я, в частности, подробно разбираю тему модульного (Unit) тестирования в PHP .

Этот учебный скрипт на PHP показывает, как можно описать некий тест в виде единственного массива вопросов и ответов. Поскольку типов вопросов может быть несколько (выбор "да"-"нет", выбор одного из нескольких вариантов, ввод числа или строки в качестве ответа), нам понадобится не просто массив, а массив массивов , каждый элемент которого будет описывать всё, что нужно для вывода и проверки очередного вопроса. Это будут записи со следующими ключами:

  • "q" - отображаемый текст вопроса;
  • "t" - тип вопроса, соответствующий нужному тегу HTML: "checkbox" для галочек "да/нет", "text" для строки или числа в качестве ответа, "select" - для списка, в котором нужно выбрать одно значение из нескольких. Выбор более одного значения реализуем придуманным нами элементом "multiselect" , представляющим из собой группу вместе обрабатываемых checkbox"ов. На самом деле, стандартный список "; break; case "text": $len = strlen ($val["a"]); echo $val["q"]." "; break; case "select": echo $val["q"]." "; break; case "multiselect": $i = explode ("|",$val["i"]); echo $val["q"].": "; foreach ($i as $number=>$item) echo $item." "; break; } echo "
    "; } echo ""; } function error_check ($q) { $question_types = array ("checkbox", "text", "select", "multiselect"); $error = ""; if (!isset($q["q"]) or empty($q["q"])) $error="Нет текста вопроса или он пуст"; else if (!isset($q["t"]) or empty($q["t"])) $error="Не указан или пуст тип вопроса"; else if (!in_array($q["t"],$question_types)) $error="Указан неверный тип вопроса"; else if (!isset($q["a"]) or empty($q["a"]) and $q["a"]!="0") $error="Нет текста ответа или он пуст"; else { if ($q["t"]=="checkbox" and !($q["a"]=="0" or $q["a"]=="1")) $error = "Для переключателя разрешены ответы 0 или 1"; else if ($q["t"]=="select" || $q["t"]=="multiselect") { if (!isset($q["i"]) or empty($q["i"])) $error="Не указаны элементы списка"; else { $i = explode ("|",$q["i"]); if (count($i)<2) $error="Нет хотя бы 2 элементов списка вариантов ответа с разделителем |"; foreach ($i as $s) if (strlen($s)<1) { $error = "Вариант ответа короче 1 символа"; break; } else { if ($q["t"]=="select" and !array_key_exists($q["a"],$i)) $error="Ответ не является номером элемента списка"; if ($q["t"]=="multiselect") { $a = explode ("|",$q["a"]); if (count($i)!=count($a)) $error="Число утверждений и ответов не совпадает"; foreach ($a as $s) if ($s!="0" and $s!="1") { $error = "Утверждение не отмечено как верное или неверное"; break; } } } } } } if (!empty($error)) { echo "

    Найдена ошибка теста: ".$error."

    Отладочная информация:

    "; print_r ($q); exit; } } function strlwr_($s){ $hi = "ABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ"; $lo = "abcdefghijklmnopqrstuvwxyzабвгдеёжзийклмнопрстуфхцчшщъыьэюя"; $len = strlen ($s); $d=""; for ($i=0; $i<$len; $i++) { $c = substr($s,$i,1); $n = strpos($c,$hi); if ($n!==FALSE) $c = substr ($lo,$n,1); $d .= $c; } return $d; } ?>

    Это первая часть серии "PHPUnit для начинающих". В этом руководстве мы объясним для чего покрывать код unit-тестами и всю мощь инструмента PHPUnit. В конце мы напишем простой тест с использованием PHPUnit.
    • PHPUnit для начинающих. Часть 1: Начните использование.

    Типы тестов

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

    Давайте разделим тесты на категории по уровню их специфичности. По данным Википедии . В целом существует 4 признанных уровня тестов:

    • Unit-тестирование (модульное): этот уровень тестирует наименьшую единицу функциональности. С точки зрения разработчика его задачей является убедиться, что тестируемая функция выполняет именно то, для чего она реализована. Таким образом, она должна быть минимально зависима или совершенно независима от другой функции или класса. Она должна быть написана таким образом, чтобы она полностью выполнялась в памяти, т.е. она не должна коннектиться к БД, не должна обращаться к сети или использовать ФС и т.д. Unit-тестирование должно быть как можно более простым.
    • Интеграционное тестирование: этот уровень "соединяет" разные единицы кода и тестирует правильно ли работают их комбинации. Он надстраивается сверху над unit-тестированием и способен отловить баги, которые нельзя выявить с помощью unit-тестирования, т.к. интеграционное тестирование проверяет, работает ли класс А с классом Б.
    • Системное тестирование: оно создано для воспроизведения работы сценариев в условиях, приближенных к боевым. Оно, в свою очередь, надстраивается сверху над интеграционным тестированием. В то время как интеграционное тестирование обеспечивает слаженную работу различных частей системы. Системное тестирование отвечает за то, что система работает так, как предполагает пользователь, прежде чем отправить её следующий уровень.
    • Приёмочное тестирование: в то время как выше приведённые тесты предназначены для разработчиков на стадии разработки, приёмочное тестирование фактически выполняется пользователями ПО. Пользователей не интересуют внутренние особенности ПО, их интересует только как работает это ПО.

    Если мы поместим типы тестов в пирамиду, выглядеть это будет так:

    Что такое PHPUnit

    Глядя на пирамиду вверху, мы можем сказать, что юнит-тестирование — это строительный материал для всех остальных видов тестирования. Когда мы закладываем сильную базу, мы способны построить устойчивое приложение. Однако написание тестов вручную, а также запуск тестов каждый раз когда вы вносите изменения — это процесс трудоёмкий. Если бы существовал инструмент для автоматизации этого процесса, то написание тестов стало бы гораздо более приятным занятием.

    Вот где выходит на сцену PHPUnit. В настоящее время PHPUnit наиболее популярный фреймворк для юнит-тестирования в PHP. Кроме наличия таких возможностей, как моки (подделки) объектов, он также может анализировать покрытие кода, логирование и предоставляет тысячи других возможностей.

    Давайте установим PHPUnit в нашей системе:

    1. Загрузите его: PHPUnit распространяется в PHAR(PHp ARhive) файле. Скачать можно .
    2. Добавьте путь к нему в системную переменную $PATH: после скачивания PHAR файла, убедитесь, что он является запускаемым (executable) и путь, где он находится, прописан в системной переменной $PATH . Т.о. вы сможете запускать его из любого места.

    Если вы работаете на Unix-подобной системе, то это вы можете сделать следующими командами:

    $ wget https://phar.phpunit.de/phpunit.phar $ chmod +x phpunit.phar $ sudo mv phpunit.phar /usr/local/bin/phpunit

    Если вы сделали всё верно, то вы сможете увидеть версию установленного PHPUnit, набрав в вашем терминале команду:

    $ phpunit --version

    Ваш первый unit-тест

    Пришло время написать ваш первый unit-тест! Для начала нам нужен какой-нибудь класс, который мы будем тестировать. Давайте напишем простенький класс под названием Calculator . И напишем для него тест.

    Создайте файл "Calculator.php" и скопируйте в него нижеприведённый код. Этот класс Calculator имеет только один метод add .

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

    Теперь создайте файл для тестов "CalculatorTest.php" и скопируйте в него следующий код. Мы остановимся на каждом методе более детально.

    Require "Calculator.php"; class CalculatorTests extends PHPUnit_Framework_TestCase { private $calculator; protected function setUp() { $this->calculator = new Calculator(); } protected function tearDown() { $this->calculator = NULL; } public function testAdd() { $result = $this->calculator->add(1, 2); $this->assertEquals(3, $result); } }

    • Line 2: подключаем файл тестируемого класса Calculator.php . Так как в этом файле мы собираемся тестировать именно этот класс, убедитесь, что он подключен.
    • Line 8: setUp() это метод, который вызывается перед каждым тестом. Запомните он вызывается перед Каждым тестом , что означает, что если вы добавите ещё один метод теста в этот класс, то он будет вызываться и перед ним тоже.
    • Line 13: аналогично методу setUp() , tearDown() вызывается после каждого теста.
    • Line 18: testAdd() — это метод-тест для метода add() . PHPUnit будет распознавать каждый метод, начинающийся с test, как метод-тест и автоматически запускать его. В действительности этот метод очень прост: сначала мы вызываем метод Calculator::add() чтобы вычислить значение 1 плюс 2, а затем мы проверяем, что этот метод вернул правильное значение, используя assertEquals() из PHPUnit.

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

    $ phpunit CalculatorTest.php

    Если вы всё сделали правильно, то вы должны увидеть что-то вроде этого:

    PHPUnit 3.7.32 by Sebastian Bergmann. . Time: 31ms, Memory: 2.25Mb OK (1 test, 1 assertion)

    Заключение

    Мы завершили первое руководство из серии "PHPUnit для начинающих". В следующей статье мы собираемся показать вам как использовать Data Provider (поставщик данных) в ваших тестах.

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

    Если вам понравился перевод на эту тему читайте нас в

    Вы, ребята, проводите модульное тестирование на PHP? Я не уверен, что я когда-либо делал это... что это такое?

    assertEquals(0, count($stack)); array_push($stack, "foo"); $this->assertEquals("foo", $stack); $this->assertEquals(1, count($stack)); $this->assertEquals("foo", array_pop($stack)); $this->assertEquals(0, count($stack)); } } ?>

    В качестве более сложного примера я хотел бы указать вам на фрагмент my на github .

    PHPUnit с охватом кода

    Мне нравится практиковать что-то под названием TDD с использованием модульной системы тестирования (в PHP, которая phpunit).

    Что мне также очень нравится в phpunit , так это то, что он также предлагает покрытие кода через xdebug. Как видно из изображения ниже, мой класс имеет 100% -ный охват тестирования. Это означает, что была проверена каждая строка из моего класса Authentication , что дает мне уверенность в том, что код делает то, что должен. Имейте в виду, что покрытие не всегда означает, что ваш код хорошо протестирован. Вы можете иметь 100% -ый охват без тестирования отдельной строки производственного кода.

    Netbeans

    Лично мне нравится тестировать свой код внутри Netbeans (для PHP). с простым щелчком мыши (alt + f6) я могу проверить весь свой код. Это означает, что мне не нужно оставлять IDE, что мне очень нравится, и помогает экономить время переключения между сеансами.

Публикации по теме

  • Как узнать название материнской платы Как узнать название материнской платы

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

  • Скачать sp flash tool версия 5 Скачать sp flash tool версия 5

    FlashTool - 5.1844.00.000 - программа FlashTool предназначена для работы с китайскими телефонами. Программа предоставляет возможность...