01/2018 / Milan Herda / feudarium.com
* zjednodušene
Druhy testov
Druhy testov
Druhy testov
Stiahnite si zdrojáky pre príklad:
goo.gl/gDNHa9
Najrozšírenejší testovací framework pre PHP.
Dajú sa s ním robiť všetky druhy testov.
Testy sa píšu ako metódy triedy odvodenej od
PHPUnit\Framework\TestCase
composer require --dev phpunit/phpunit
vendor/bin/phpunit
--bootstrap=vendor/autoload.php
--color
tests/
Akceptačný test v PHPUnit
Akceptačný test v PHPUnit
composer require --dev fabpot/goutte
Napr. Goutte: postavený nad BrowserKit a DomCrawler zo Symfony
Akceptačný test v PHPUnit
namespace tests\Acceptance;
use Goutte\Client;
use PHPUnit\Framework\TestCase;
class AddTodoTest extends TestCase
{
public function testAddTodo()
{
$myTodoText = 'Môj prvý akceptačný test';
$client = new Client();
$crawler = $client->request('GET', 'http://localhost:8080/');
$form = $crawler->selectButton('Pridaj úlohu')->form();
$form->setValues([
'text' => $myTodoText,
]);
$crawler = $client->submit($form);
$this->assertEquals(200, $client->getResponse()->getStatus());
$nodes = $crawler->filter('li.list-group-item:contains("' . $myTodoText . '")');
$this->assertEquals(1, $nodes->count());
}
}
Akceptačný test v PHPUnit
vendor/bin/phpunit --bootstrap=vendor/autoload.php --color tests/
Akceptačný test v PHPUnit
Akceptačný test v PHPUnit
Akceptačný test v PHPUnit
Fixný a známy stav objektov a dát, nad ktorým vieme spustiť test.
Akceptačný test v PHPUnit
[]
tests/Acceptance/fixtures/addTodo.json
Akceptačný test v PHPUnit
// tests/Acceptance/AddTodoTest.php
protected function loadDbFixtures(string $fixturePath)
{
copy($fixturePath, __DIR__ . '/../../db/data.json');
}
public function testAddTodo()
{
$this->loadDbFixtures(__DIR__ . '/fixtures/addTodo.json');
$myTodoText = 'Môj prvý akceptačný test';
// ...
}
Akceptačný test v PHPUnit
#!/bin/bash
cp db/data.json db/data.bak.json
vendor/bin/phpunit --bootstrap=vendor/autoload.php --color $@
mv db/data.bak.json db/data.json
Vytvoríme spustiteľný súbor bin/run-tests
Akceptačný test v PHPUnit
bin/run-tests tests/
Akceptačný/Integračný test v PHPUnit
Integračný test v PHPUnit
Integračný test v PHPUnit
[
{
"id": 11,
"text": "prve todo",
"createdAt": "2018-01-01 01:01:01",
"isDone": false
},
{
"id": 22,
"text": "druhe todo",
"createdAt": "2018-02-02 02:02:02",
"isDone": true
}
]
Súbor tests/Integration/DataProvider/fixtures/findAll.json
Integračný test v PHPUnit
namespace tests\Integration\DataProvider;
class TodoDataProviderTest extends TestCase
{
public function testFindAll()
{
$pathToFile = __DIR__ . '/fixtures/findAll.json';
$converter = new TodoItemConverter();
$provider = new TodoDataProvider($pathToFile, $converter);
$items = $provider->findAll();
$this->assertCount(2, $items);
$firstTodo = $items[0];
$this->assertInstanceOf(TodoItem::class, $firstTodo);
$this->assertEquals(11, $firstTodo->getId());
$this->assertEquals('prve todo', $firstTodo->getText());
$this->assertEquals(
'2018-01-01 01:01:01',
$firstTodo->getCreatedAt()->format('Y-m-d H:i:s')
);
$this->assertEquals(false, $firstTodo->isDone());
}
}
Integračný test v PHPUnit
Testami by sme mali pokryť pokiaľ možno všetky situácie, ktoré vieme predvídať.
Unit test v PHPUnit
Všetko, čo nepatrí tejto jednotke, musí byť nahradené dablérmi plne pod kontrolou testu.
Unit test v PHPUnit
namespace tests\Unit\DataConverter;
class TodoItemConverterTest extends TestCase
{
public function testConvertToEntity()
{
$converter = new TodoItemConverter();
$data = [
'id' => 111,
'text' => 'lorem ipsum',
'createdAt' => '2018-01-01 01:01:01',
'isDone' => false,
];
$entity = $converter->convertToEntity($data);
$this->assertInstanceOf(TodoItem::class, $entity);
$this->assertEquals($data['id'], $entity->getId());
$this->assertEquals($data['text'], $entity->getText());
$this->assertEquals($data['createdAt'], $entity->getCreatedAt()->format('Y-m-d H:i:s'));
$this->assertEquals($data['isDone'], $entity->isDone());
}
}
Unit test v PHPUnit
namespace tests\Unit\DataConverter;
class TodoItemConverterTest extends TestCase
{
public function testConvertToArray()
{
$converter = new TodoItemConverter();
$todo = new TodoItem();
$todo->setId(222)
->setText('Star Trek Ipsum')
->setCreatedAt(new \DateTime('2018-02-02 02:02:02'))
->setAsNotDone();
$data = $converter->convertToArray($todo);
$this->assertEquals(222, $data['id']);
$this->assertEquals('Star Trek Ipsum', $data['text']);
$this->assertEquals('2018-02-02 02:02:02', $data['createdAt']);
$this->assertFalse($data['isDone']);
}
}
Unit test v PHPUnit
testConvertToArray pracuje s dvoma triedami:
TodoItem je však entita (DTO, Value Object), takže to až tak nevadí
Striktne vzaté sa jedná o integračný test.
Unit test v PHPUnit
Unit test v PHPUnit
Závislosť na TodoDataProvider nahradíme dablérom
Unit test v PHPUnit
namespace tests\Unit\Repository;
class TodoRepositoryTest extends TestCase
{
public function testFindAll()
{
$dataProvider = /* Ako vytvoriť dataProvider? */;
$repository = new TodoRepository($dataProvider);
$items = $repository->findAll();
$expectedItems = /* ... */;
$this->assertEquals($expectedItems, $items);
}
}
Unit test v PHPUnit
// ...
$dataProvider = /* Ako vytvoriť dataProvider? */;
// ...
Nesmie to byť inštancia triedy TodoDataProvider, pretože táto trieda nie je predmetom testu a nemáme ju v teste pod kontrolou.
Unit test v PHPUnit
class TestFindAllDataProvider extends TodoDataProvider
{
public function findAll()
{
return 'hodnota len pre účel testu';
}
}
Vytvoríme triedu, ktorá bude potomkom pôvodného provideru
a bude sa používať len pre tento test.
Unit test v PHPUnit
Takejto "náhradnej" triede sa hovorí "mock".
Našťastie ich nemusíme vytvárať ručne, ale existujú k tomu knižnice.
Môžeme použiť priamo PHPUnit.
Ale prečo by sme tancovali vo zvieracej kazajke, keď máme alternatívu?
Unit test v PHPUnit
composer require --dev mockery/mockery
Unit test v PHPUnit
class TodoRepositoryTest extends TestCase
{
protected function tearDown()
{
Mockery::close();
}
public function testFindAll()
{
$itemA = Mockery::mock(TodoItem::class);
$itemB = Mockery::mock(TodoItem::class);
$dataProvider = Mockery::mock(TodoDataProvider::class);
$dataProvider->shouldReceive('findAll')->once()->andReturn([$itemA, $itemB]);
$repository = new TodoRepository($dataProvider);
$items = $repository->findAll();
$this->assertEquals([$itemA, $itemB], $items);
}
}
Unit test v PHPUnit
public function testAddTodo()
{
$newId = 23;
$newItem = Mockery::mock(TodoItem::class);
$newItem->shouldReceive('getId')->andReturn($newId);
$itemA = Mockery::mock(TodoItem::class);
$itemA->shouldReceive('getId')->andReturn(11);
$itemB = Mockery::mock(TodoItem::class);
$itemB->shouldReceive('getId')->andReturn(22);
$dataProvider = Mockery::mock(TodoDataProvider::class);
$dataProvider->shouldReceive('findAll')->once()->andReturn([$itemA, $itemB]);
$newItem->shouldReceive('setId')->once()->with($newId)->andReturnSelf();
$dataProvider->shouldReceive('saveItems')->once()->with([
$itemA,
$itemB,
$newItem
]);
$repository = new TodoRepository($dataProvider);
$id = $repository->addTodo($newItem);
$this->assertEquals($newId, $id);
}
Unit test v PHPUnit
Tieto sa najlepšie mockujú pomocou knižnice Patchwork
Pri vývoji spúšťame priebežne testy aspoň pre nový kód.
Po dokončení tasku spustíme všetky testy.
Najlepšie je mať testy spúšťané aj v CI pre každý Pull Request a po každom merge-i.
Všetky!
Keď sa s testami začína a všetci sa ich učia, tak áno