Тестування з допомогою PHPUnit

       Андрій Насінник




           Тернопіль
         Березень 2013
- Трохи історії
- Інсталяція PHPUnit
- Структура тесту
- Asserts
- Написання Тестів
- Data Providers
- Mock Objects
- Трохи про TDD
- Два слова про BDD
Трохи історії

SUnit - xUnit framework на Smalltalk (Kent Beck)

JUnit - порт xUnit на Java (Kent Beck & Erich Gamma)

PHPUnit - порт xUnit на PHP (Sebastian Bergmann)

SimpleTest - конкурет PHPUnit
Інсталяція PHPUnit

- Pear

pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit


- Phar

wget http://pear.phpunit.de/get/phpunit.phar
chmod +x phpunit.phar
Інсталяція PHPUnit

- Composer (composer.json)

{
    "name": "phpunit",
    "description": "PHPUnit",
    "require": {
        "phpunit/phpunit": "3.8.*"
    },
    "config": {
        "bin-dir": "/usr/local/bin/"
    }
}



curl -s https://getcomposer.org/installer | php
./composer.phar install
Структура тесту

- Базовий інтерфейс

TestCase/TestSuite implements PHPUnit_Framework_Test
public function run(PHPUnit_Framework_TestResult $result);


- Запуск

$test->run($result):
    $result->run($test):
        $test->setUpBeforeClass() //once per class
        $test->setUp()
        $test->runTest()
        $test->tearDown()
        $test->tearDownAfterClass() //once per class
Структура тесту

- Інтерфейс suite`a

PHPUnit_Framework_TestSuite implements IteratorAggregate


- Запуск тестів з suite’а

foreach ($suite as $test) {
    $test->run();
}
Asserts

- TestCase містить 90 методів assert

$this->assertEquals('expected', 'actual', 'custom message');
$this->assertThat('expected',
    PHPUnit_Framework_Constraint $constraint, 'custom
message');


- 3 "marked" function

markTestIncomplete();
markTestSkipped();
fail();
Asserts

- 6 "constraint" методів

equalTo(); isNull(); isTrue(); isType(); isEmpty();


- 4 "logic" методи

logicalOr(); logicalXor(); logicalAnd(); logicalNot();


$constraint = $this->logicalAnd(
    $this->isType('string'),
    $this->equalTo('test')
);
$this->assertThat($value, $constraint, $message);
Написання Тестів

class FirstTest extends PHPUnit_Framework_TestCase
{
    public function testFirst()
    {
         $this->assertTrue(true);
    }
}

class FirstTest extends PHPUnit_Framework_TestCase
{
    /**
      * @test
      */
    public function first()
    {
         $this->assertTrue(true);
    }
}
Data Providers

public function firstDataProvider()
{
    return array(
        'empty' => array(''),
        'incorrect' => array('@#^&!^#*!#')
    );
}


/**
  * @test
  * @dataProvider firstDataProvider
  */
public function first($data)
{
     $this->assertFalse($object->validate($data));
}
Mock Objects

Мокаєм обєкт:
/** @return PHPUnit_Framework_MockObject_MockObject */
public function getMock($objectName, $methods, $parameters,
$mockName, $callConstructor, $callClone, $callAutoload,
$cloneArguments)


Invocation Matcher:

$mock->expects($this->any());


Matcher Types:

any(), never(), once(), atLeastOnce(),
exactly(count), at(index)
Mock Objects

Перевірка параметрів:

$mock->expects($this->any())
    ->method('test')
    ->with($arg1, $arg2);//аргумент   - constraint, по дефолту equalTo



Емуляція результату:

$mock->expects($this->any())
    ->method('test')
    ->will(
        $this->returnValue($data)
    );//PHPUnit_Framework_MockObject_Stub
Mock Objects

Формування Stubs:

$ths->returnValue($data);
$ths->returnValueMap(
    array(
        array('p1', 'p2', 'r'),
        array('p2', null, 'r2'),//з дефолтними параметрами
    )
);
$ths->returnCallback(function ($data) {
    return explode('/', $data)
});
$ths->returnSelf();//return theme self
$ths->returnArgument(argIndex);
Mock Objects

Приклад тесту з використанням мокінгу:

public function testMocking()
{
    $mock = $this->getMock('DbAdapterPdoMysql', array
('fetchRow'));
    $mock->expects($this->exactly(2))
            ->method('fetchRow')
            ->with($this->logicalOr('Query', 'Query2'))
            ->will(
                $this->returnValue(
                    array('min' => 1, 'max' => 300)
                )
            );

    $object = new Enterprise_Index_Model_ICCP_Refresh($mock);
    $object->rebuildIndex();
}
Трохи про TDD
Трохи про TDD

Простий приклад TDD

Спершу тест:

class EmailTest extends PHPUnit_Framework_TestCase
{
    public function testGetDomainFromEmail()
    {
        $e = new Email();
        $this->assertEquals(
            'example.com',
            $e->getDomainFromEmail('test@examlple.com')
        );
    }
}


Запускаєм тест. Результат Error.
Трохи про TDD

Найпростішим способом заставляєм працювати тест.
Підміняєм реалізацію (Fake It).

class Email
{
    public function getDomainFromEmail($email)
    {
        return 'example.com';
    }
}


Зелена плоса. Тест спрацьовує.
Трохи про TDD

Рефакторимо. Видаляємо дублювання.

class Email
{
    public function getDomainFromEmail($email)
    {
        return explode('@', $email)[1];
    }
}


Зелена плоса. Тест проходить. Метод працює так як нам
потрібно.
Трохи про TDD

Ще один приклад

Спершу тест:

class EmailTest extends PHPUnit_Framework_TestCase
{
    public function testIsEmailValid()
    {
        $v = new Validator();
        $this->assertTrue(
           $v->isEmailValid('test@examlple.com')
        );
    }
}


Запускаєм тест. Результат Error.
Трохи про TDD

Найпростішим способом заставляєм працювати тест.

class Validator
{
    public function isEmailValid($email)
    {
        return true;
    }
}


Зелена плоса. Тест проходить.
Трохи про TDD

Добавимо більше тестових випадків (Triangulate).

public function isEmailValidDataProvider()
{
    return array(
        'correct' => array('test@example.com', true),
        'incorrect' => array('incorrect', false),
    );
}
public function testIsEmailValid($email, $expectedResult)
{
    //...
    $this->assertEquals($expectedResult, $v->isEmailValid
($email));
}
Запускаєм і на цей раз бачим Fail.
Далі пишемо правильну реалізацію.
Трохи про TDD

Якщо код зрозуміло пишіть зразу правильний тест.
(Oblivious Implementation).
Два слова про BDD

- Базується на TDD.
- Описує роботу Бізнесу та Девелоперів.
- Бізнес пише специфікації в вигляді тестів або
специфікації генеруються в тести.
- Девелопери пишуть код так щоб всі тести
спрацьовували.

Рішення для PHP:
Codeception
Behat
PHPUnit_Story
Матеріали

Книжки:

Kent Beck - XP. Test Driven Development
Martin Fowler - Refactoring: Improving the Design of Existing Code


Лінки:

http://www.phpunit.de/manual/3.8/en/index.html
http://behat.org/
http://codeception.com/
http://getcomposer.org/
http://en.wikipedia.org/wiki/Test-driven_development
http://en.wikipedia.org/wiki/Behavior-driven_development
Дякую за увагу!

Андрій Насінник

Копанія:
Magento

Email:
andriy.nas@gmail.com

https://github.com/Nas1k

Skype:
nas_ua

Тестування з допомогою PHPUnit

  • 1.
    Тестування з допомогоюPHPUnit Андрій Насінник Тернопіль Березень 2013
  • 2.
    - Трохи історії -Інсталяція PHPUnit - Структура тесту - Asserts - Написання Тестів - Data Providers - Mock Objects - Трохи про TDD - Два слова про BDD
  • 3.
    Трохи історії SUnit -xUnit framework на Smalltalk (Kent Beck) JUnit - порт xUnit на Java (Kent Beck & Erich Gamma) PHPUnit - порт xUnit на PHP (Sebastian Bergmann) SimpleTest - конкурет PHPUnit
  • 4.
    Інсталяція PHPUnit - Pear pearconfig-set auto_discover 1 pear install pear.phpunit.de/PHPUnit - Phar wget http://pear.phpunit.de/get/phpunit.phar chmod +x phpunit.phar
  • 5.
    Інсталяція PHPUnit - Composer(composer.json) { "name": "phpunit", "description": "PHPUnit", "require": { "phpunit/phpunit": "3.8.*" }, "config": { "bin-dir": "/usr/local/bin/" } } curl -s https://getcomposer.org/installer | php ./composer.phar install
  • 6.
    Структура тесту - Базовийінтерфейс TestCase/TestSuite implements PHPUnit_Framework_Test public function run(PHPUnit_Framework_TestResult $result); - Запуск $test->run($result): $result->run($test): $test->setUpBeforeClass() //once per class $test->setUp() $test->runTest() $test->tearDown() $test->tearDownAfterClass() //once per class
  • 7.
    Структура тесту - Інтерфейсsuite`a PHPUnit_Framework_TestSuite implements IteratorAggregate - Запуск тестів з suite’а foreach ($suite as $test) { $test->run(); }
  • 8.
    Asserts - TestCase містить90 методів assert $this->assertEquals('expected', 'actual', 'custom message'); $this->assertThat('expected', PHPUnit_Framework_Constraint $constraint, 'custom message'); - 3 "marked" function markTestIncomplete(); markTestSkipped(); fail();
  • 9.
    Asserts - 6 "constraint"методів equalTo(); isNull(); isTrue(); isType(); isEmpty(); - 4 "logic" методи logicalOr(); logicalXor(); logicalAnd(); logicalNot(); $constraint = $this->logicalAnd( $this->isType('string'), $this->equalTo('test') ); $this->assertThat($value, $constraint, $message);
  • 10.
    Написання Тестів class FirstTestextends PHPUnit_Framework_TestCase { public function testFirst() { $this->assertTrue(true); } } class FirstTest extends PHPUnit_Framework_TestCase { /** * @test */ public function first() { $this->assertTrue(true); } }
  • 11.
    Data Providers public functionfirstDataProvider() { return array( 'empty' => array(''), 'incorrect' => array('@#^&!^#*!#') ); } /** * @test * @dataProvider firstDataProvider */ public function first($data) { $this->assertFalse($object->validate($data)); }
  • 12.
    Mock Objects Мокаєм обєкт: /**@return PHPUnit_Framework_MockObject_MockObject */ public function getMock($objectName, $methods, $parameters, $mockName, $callConstructor, $callClone, $callAutoload, $cloneArguments) Invocation Matcher: $mock->expects($this->any()); Matcher Types: any(), never(), once(), atLeastOnce(), exactly(count), at(index)
  • 13.
    Mock Objects Перевірка параметрів: $mock->expects($this->any()) ->method('test') ->with($arg1, $arg2);//аргумент - constraint, по дефолту equalTo Емуляція результату: $mock->expects($this->any()) ->method('test') ->will( $this->returnValue($data) );//PHPUnit_Framework_MockObject_Stub
  • 14.
    Mock Objects Формування Stubs: $ths->returnValue($data); $ths->returnValueMap( array( array('p1', 'p2', 'r'), array('p2', null, 'r2'),//з дефолтними параметрами ) ); $ths->returnCallback(function ($data) { return explode('/', $data) }); $ths->returnSelf();//return theme self $ths->returnArgument(argIndex);
  • 15.
    Mock Objects Приклад тестуз використанням мокінгу: public function testMocking() { $mock = $this->getMock('DbAdapterPdoMysql', array ('fetchRow')); $mock->expects($this->exactly(2)) ->method('fetchRow') ->with($this->logicalOr('Query', 'Query2')) ->will( $this->returnValue( array('min' => 1, 'max' => 300) ) ); $object = new Enterprise_Index_Model_ICCP_Refresh($mock); $object->rebuildIndex(); }
  • 16.
  • 17.
    Трохи про TDD Простийприклад TDD Спершу тест: class EmailTest extends PHPUnit_Framework_TestCase { public function testGetDomainFromEmail() { $e = new Email(); $this->assertEquals( 'example.com', $e->getDomainFromEmail('test@examlple.com') ); } } Запускаєм тест. Результат Error.
  • 18.
    Трохи про TDD Найпростішимспособом заставляєм працювати тест. Підміняєм реалізацію (Fake It). class Email { public function getDomainFromEmail($email) { return 'example.com'; } } Зелена плоса. Тест спрацьовує.
  • 19.
    Трохи про TDD Рефакторимо.Видаляємо дублювання. class Email { public function getDomainFromEmail($email) { return explode('@', $email)[1]; } } Зелена плоса. Тест проходить. Метод працює так як нам потрібно.
  • 20.
    Трохи про TDD Щеодин приклад Спершу тест: class EmailTest extends PHPUnit_Framework_TestCase { public function testIsEmailValid() { $v = new Validator(); $this->assertTrue( $v->isEmailValid('test@examlple.com') ); } } Запускаєм тест. Результат Error.
  • 21.
    Трохи про TDD Найпростішимспособом заставляєм працювати тест. class Validator { public function isEmailValid($email) { return true; } } Зелена плоса. Тест проходить.
  • 22.
    Трохи про TDD Добавимобільше тестових випадків (Triangulate). public function isEmailValidDataProvider() { return array( 'correct' => array('test@example.com', true), 'incorrect' => array('incorrect', false), ); } public function testIsEmailValid($email, $expectedResult) { //... $this->assertEquals($expectedResult, $v->isEmailValid ($email)); } Запускаєм і на цей раз бачим Fail. Далі пишемо правильну реалізацію.
  • 23.
    Трохи про TDD Якщокод зрозуміло пишіть зразу правильний тест. (Oblivious Implementation).
  • 24.
    Два слова проBDD - Базується на TDD. - Описує роботу Бізнесу та Девелоперів. - Бізнес пише специфікації в вигляді тестів або специфікації генеруються в тести. - Девелопери пишуть код так щоб всі тести спрацьовували. Рішення для PHP: Codeception Behat PHPUnit_Story
  • 25.
    Матеріали Книжки: Kent Beck -XP. Test Driven Development Martin Fowler - Refactoring: Improving the Design of Existing Code Лінки: http://www.phpunit.de/manual/3.8/en/index.html http://behat.org/ http://codeception.com/ http://getcomposer.org/ http://en.wikipedia.org/wiki/Test-driven_development http://en.wikipedia.org/wiki/Behavior-driven_development
  • 26.
    Дякую за увагу! АндрійНасінник Копанія: Magento Email: andriy.nas@gmail.com https://github.com/Nas1k Skype: nas_ua