Юніт тести в php за допомогою phpUnit (частина перша)

Про 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&gt;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:

  1. The tests for a class Class go into a class ClassTest.
  2. ClassTest inherits (most of the time) from PHPUnit_Framework_TestCase.
  3. 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.

  4. 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. За більш детальною інформацією звертайтесь до офіційного мануалу.

2 thoughts on “Юніт тести в php за допомогою phpUnit (частина перша)

  1. Дякую за статтю, Степане, я саме шукав, як визвати тести за допомогою php, без використання утиліти phpunit.

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>