Про TDD можна почитати на agiledev.ru, а тут я хочу розповісти про створення автоматизованих юніт тестів за допомогою бібліотеки phpUnit.
PEAR::PHPUnit це бібліотека для створення автоматизованих тестів. Автор Sebastian Bergmann. Сайт бібліотеки http://www.phpunit.de.
Інсталюємо phpUnit
Для Widows я качав PHPUnit-3.3.4 з http://pear.phpunit.de/get/.
Копіюємо каталог PHPUnit в каталог PEAR (не забуваємо про include_path).
Перший тест
Створюємо окремий каталог для тестів, а в ньому файл ArrayTest.php наступного вмісту:
<?php require_once 'PHPUnit/Framework.php'; require_once 'PHPUnit/Framework/IncompleteTestError.php'; require_once 'PHPUnit/Framework/TestCase.php'; require_once 'PHPUnit/Framework/TestSuite.php'; require_once 'PHPUnit/Runner/Version.php'; require_once 'PHPUnit/TextUI/TestRunner.php'; require_once 'PHPUnit/Util/Filter.php'; class ArrayTest extends PHPUnit_Framework_TestCase { public static function main() { $suite = new PHPUnit_Framework_TestSuite("ArrayTest"); $result = PHPUnit_TextUI_TestRunner::run($suite); } public function testNewArrayIsEmpty() { // Create the Array fixture. $fixture = array(); // Assert that the size of the Array fixture is 0. $this->assertEquals(0, sizeof($fixture)); } public function testArrayContainsAnElement() { // Create the Array fixture. $fixture = array(); // Add an element to the Array fixture. $fixture[] = 'Element'; // Assert that the size of the Array fixture is 1. $this->assertEquals(1, sizeof($fixture)); } } ArrayTest::main(); |
Запускаємо і якщо все нормально, то бачимо результат тестування:
Z:\home\test\phpunit\php ArrayTest.php PHPUnit 3.2.15 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions) |
Отже, виконано 2ва тести – помилок немає.
Але можна робити це все ще простіше. Трохи змінимо вміст ArrayTest.php:
<?php class ArrayTest extends PHPUnit_Framework_TestCase { public function testNewArrayIsEmpty() { // Create the Array fixture. $fixture = array(); // Assert that the size of the Array fixture is 0. $this->assertEquals(0, sizeof($fixture)); } public function testArrayContainsAnElement() { // Create the Array fixture. $fixture = array(); // Add an element to the Array fixture. $fixture[] = 'Element'; // Assert that the size of the Array fixture is 1. $this->assertEquals(1, sizeof($fixture)); } } |
Якщо просто запустити цей файл на виконання, то нічого не відбудеться. Що ж робити?
Візьмемо файл PHPUnit\pear-phpunit.bat, скопіюємо його в каталог з тестами і трохи відредагуємо.
Ось його першочерговий вигляд:
@echo off REM PHPUnit REM bla-bla-bla set PHPBIN="@php_bin@" "@php_bin@" -d safe_mode=Off "@php_dir@/PHPUnit/TextUI/Command.php" %* |
Замість @php_bin@ потрібно підставити шлях до php.exe (в мене шлях до директорії з php вже збережений в змінній середовища Path), а замість @php_dir@ шлях до PHPUnit.
Мій pear-phpunit.bat виглядає таким чином:
@echo off php -d safe_mode=Off "z:\usr\local\php5\PEAR\PHPUnit\TextUI\Command.php" %* |
Запускаємо наш тест:
Z:\home\test\phpunit>pear-phpunit ArrayTest.php PHPUnit 3.3.4 by Sebastian Bergmann. .. Time: 0 seconds OK (2 tests, 2 assertions) |
Ми отримали той самий результат, але за менших зусиль. В подальшому я буду використовувати саме цей спосіб.
Що потрібно знати
В розділі Writing Tests for PHPUnit (з якого я витягнув приклад тесту) сказано наступне:
Example 4.1 shows the basic steps for writing tests with PHPUnit:
- The tests for a class Class go into a class ClassTest.
- ClassTest inherits (most of the time) from PHPUnit_Framework_TestCase.
- The tests are public methods that are named test*.
Alternatively, you can use the @test annotation in a method’s docblock to mark it as a test method.
- Inside the test methods, assertion methods such as assertEquals() (see the section called “PHPUnit_Framework_Assert”) are used to assert that an actual value matches an expected value.
Я розумію це приблизно так:
1. Тест класу Class потрібно називати ClassTest.
2. ClassTest (у більшості випадків) наслідується від PHPUnit_Framework_TestCase.
3. Тестовими методами вважаються public методи, назва яких починається з test (наприклад testArrayContainsAnElement). Альтернативою є використання тегу @test в docblock.
4. В середині методів потрібно використовувати методи перевірки типу assertEquals(), щоб порівняти фактичне значення змінної з її очікуваним значенням.
Провайдер данних
За допомогою тега @dataProvider можна вказати назву метода, який буде надсилати вхідні дані для тесту. Створимо новий файл DataTest.php.
<?php class DataTest extends PHPUnit_Framework_TestCase { /** * @dataProvider provider */ public function testAdd($a, $b, $c) { $this->assertEquals($c, $a + $b); } public function provider() { return array( array(0, 0, 0), array(0, 1, 1), array(1, 0, 1), array(1, 1, 3) ); } } |
В docblock метода testAdd() вказано, що вхідні данні для тесту поверне метод provider(). Запускаємо тест (я перейменував наш bat файл в phpunit.bat і закинув його в директорію php):
phpunit DataTest PHPUnit 3.3.4 by Sebastian Bergmann. ...F Time: 0 seconds There was 1 failure: 1) testAdd(DataTest) with data set #3 (1, 1, 3) Failed asserting that <integer:2> matches expected value <integer:3>. Z:hometestphpunitDataTest.php:9 FAILURES! Tests: 4, Assertions: 4, Failures: 1. |
Бачимо, що не виконався один тест. А саме testAdd(), на вхід якого подали data set #3 (1, 1, 3). І приписка, що порівнювались числа 2 ($a+$b=1+1) і 3 ($c=3). Виправляємо помилку в 3му наборі вхідних даних (нумерація в масивах починається з нуля, тому в 3му), запускаємо – все працює.
Ловимо виняткові ситуації (exceptions)
Що робити, якщо потрібно зробити тест на наявність експешена певного типу? Розглянемо наступний приклад:
<?php class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } } |
“@expectedException InvalidArgumentException” означає, що в методі testException() повинен генеруватися ексепшн InvalidArgumentException. І поки його там не буде, ми будемо бачити наступне:
phpunit ExceptionTest PHPUnit 3.3.4 by Sebastian Bergmann. F Time: 0 seconds There was 1 failure: 1) testException(ExceptionTest) Expected exception InvalidArgumentException FAILURES! Tests: 1, Assertions: 1, Failures: 1. |
Додаємо генерацію винятку:
<?php class ExceptionTest extends PHPUnit_Framework_TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { throw new InvalidArgumentException('myexception!'); } } |
Запускаємо:
phpunit ExceptionTest PHPUnit 3.3.4 by Sebastian Bergmann. . Time: 0 seconds OK (1 test, 1 assertion) |
Все працює.
І на сьогодні, мабуть, все – далі буде :).
PS. За більш детальною інформацією звертайтесь до офіційного мануалу.
Дякую за статтю, Степане, я саме шукав, як визвати тести за допомогою php, без використання утиліти phpunit.
Радий, що Вам вона допомогла ;).