Testování prakticky
@ProchazkaFilip
Co si povíme
- troška teorie
- nette/tester
- mockery/mockery
- testování proti síti
- integrační testy
- codeception/codeception
- selenium
Troška teorie
- co testovat
- že to funguje
- že to nefunguje (když nemá)
- jak testovat
- unit testy
- integrační testy
- akceptační testy
- selenium (high level akceptační testy)
- Test Driven Development (red, green, refactor)
- Behaviour Driven Development
nette/tester
tester: motivace
$obj = new Math;
$result = $obj->add(1, 2);
var_dump($result);
tester: motivace
$obj = new Math;
$result = $obj->add(1, 2);
Assert::same(3, $result);
tester: tests/bootstrap.php
require __DIR__ . '/../vendor/autoload.php';
TesterEnvironment::setup();
date_default_timezone_set('Europe/Prague');
tester: tests/math/add.phpt
require __DIR__ . '/../bootstrap.php';
$obj = new Math;
$result = $obj->add(1, 2);
Assert::same(3, $result);
tester: TesterAssert
Assert::same($expected, $actual);
Assert::equal($expected, $actual);
Assert::contains($needle, $actual);
Assert::true($value);
Assert::false($value);
Assert::type($type, $value);
// ...
tester: TesterAssert
$obj = new Math();
Assert::exception(function() use ($obj) {
$obj->divide(2, 0);
}, InvalidArgumentException::class, 'Cannot divide by zero');
tester: TestCase
class SomeTest extends TesterTestCase {
function setUp() { }
function tearDown() { }
function testOne() {
Assert::same(......);
}
}
(new SomeTest)->run();
tester: TestCase @throws
/**
* @throws InvalidArgumentException
*/
public function testOne()
{
// ...
}
tester: TestCase @dataProvider
public function dataLoop() {
return [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
];
}
/** @dataProvider dataLoop */
public function testLoop($a, $b, $c) {
// ...
}
tester: TestCase @dataProvider
public function dataLoop() {
yield [1, 2, 3],
yield [4, 5, 6],
yield [7, 8, 9],
}
/** @dataProvider dataLoop */
public function testLoop($a, $b, $c) {
// ...
}
tester: metadata
<?php
/**
* @testCase
*/
require __DIR__ . '/../bootstrap.php';
class SomeTest extends TesterTestCase {
_____ ___ ___ _____ ___ ___
|_ _/ __)( __/_ _/ __)| _ )
|_| ___ /___) |_| ___ |_|_ v1.6.1
PHP 7.0.0 | 'php-cgi' -n | 10 threads
........s................F.........
-- FAILED: tests/greeting.phpt
Failed: 'Hello John' should be
... 'Hi John'
in src/Framework/Assert.php(370)
in src/Framework/Assert.php(52) TesterAssert::fail()
in tests/greeting.phpt(6) TesterAssert::same()
FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)
tester: a další...
- data providery & multipliery (meta)
- skipování testů
- DomQuery na testování HTML & XML
- file mock
- zámky (aby se nemlátily paralelní testy)
- watch (automatické spouštění testů při změně kódu)
- generování code coverage
- podpora HHVM
mockery/mockery
mockery: testovaná třída
class Temperature {
function __construct(WeatherApi $weather);
function average() {
$total = 0;
for ($i = 0; $i < 3 ;$i++) {
$total += $this->weather->readTemp();
}
return $total / 3;
}
mockery: test v Testeru
class TemperatureTest extends TesterTestCase {
function testGetsAverageTemperature() {
$service = Mockery::mock(WeatherApi::class);
$service->shouldReceive('readTemp')
->times(3)->andReturn(10, 12, 14);
$temperature = new Temperature($service);
Assert::same(12, $temperature->average());
}
mockery: test v Testeru
// ...
function tearDown() {
Mockery::close();
TesterEnvironment::$checkAssertions = FALSE;
}
Testování proti síti
Testování proti síti
- je to pomalé
- síť nemusí fungovat
- formát/protocol se může změnit
- služba může mít výpadky
Testování proti síti
try {
$result = $paymentProcessor->process($order);
} catch (OrdersCardPaymentException $e) {
if (preg_match('~SoapClient.*? Connection (refused|timed out)~', $e->getMessage())) {
TesterEnvironment::skip($msg);
}
throw $e;
}
Testování proti síti
- knihovna používá psr/http-message
- výchozí je guzzle klient
- testy používají fake klienta
- api se reálně zavolá pouze poprvé
- výsledek se uloží
- další běhy načítají odpovědi z disku
- když soubor smažu, request se provede
Ukázka v testech Kdyby/CsobPaymentGateway na githubu
Testování proti síti
- můžu (alespoň částečně) vyvíjet offline
- testy náhodně nepadají
- testy jsou rychlé
Integrační testy
Integrační testy
abstract class IntegrationTestCase extends TestCase {
function getService($type);
function getContainer() {}
function createContainer() {}
abstract class DbTestCase extends IntegrationTestCase {
function createContainer() {}
function setupDatabase(Connection $db);
Integrační testy: databáze
class CartTest extends IntegrationTestCase {
function testAdd() {
$cart = $this->getService(OrderCart::class);
$cart->addItem(10); // položka s id 10
$itemsInDb = $this->getService(Connection::class)
->query("SELECT id FROM order_items WHERE order = 1");
Assert::same([10], $itemsInDb);
}
Integrační testy: presentery
abstract class PresenterTestCase extends DbTestCase {
function usePresenter($name);
function runPresenterAction($action, ...);
function makeAjax();
function fakeCsrfProtection();
function fakeRedirectFromWebpay($action, ...);
function fakeRedirectFromCsob($action, ...);
Integrační testy: presentery
class HomepagePresenterTest extends PresenterTestCase {
function setUp() {
parent::setUp();
$this->usePresenter('Front:Homepage');
}
function testRenderDefault() {
$response = $this->runPresenterAction('default');
Assert::type(TextResponse::class, $response);
}
Integrační testy: komponenty
abstract class ComponentTestCase extends DbTestCase {
function attachToPresenter($component, ...);
function loadState($params, ...);
function runSignal($signal, $params, ...);
function makeAjax();
function assertRedirect($url);
function assertSnippets($snippets);
Integrační testy: komponenty
class CartControlTest extends ComponentTestCase {
function testCopyItem() {
$basket = $this->getService(Cart::class);
$item = $basket->addItem(719693);
$control = $this->getService(ICartControlFactory::class)->create();
$this->attachToPresenter($control);
$this->runSignal('copyItem', ['item' => $item->getId()]);
// ...
Integrační testy: komponenty
// ...
Assert::same(2, $item->getCount());
$this->assertSnippets([
'snippet-user-panel',
'snippet-review',
'snippet-control-cart' => '%A%Milka Tender oříšková%A%'
]);
codeception/codeception
codeception: tests/acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- PhpBrowser:
url: 'http://127.0.0.1:8000'
- HelperAcceptance
codeception: simple test
$I = new AcceptanceTester($scenario);
$I->wantTo('see note can be successfully created');
$I->testLogin();
$I->amOnPage('/notes/create');
$I->fillField('Name', 'Example note');
$I->fillField('Text', 'Lorem ipsum');
$I->selectOption('Pad', 1);
$I->click('Save');
$I->see('Example note');
selenium
codeception: tests/acceptance.suite.yml
class_name: WebGuy
modules:
enabled:
- CodeceptionModuleWebDriver
- SeleniumTestsSupportHelperNetteSetup
config:
CodeceptionModuleWebDriver:
url: 'http://www.rohlik.l/'
browser: 'firefox'
codeception: cest test
class SigninCest {
function loginWithPassword(Homepage $homepage) {
$homepage->open();
$homepage->login('filip@prochazka.su', '12345678');
$homepage->seeThatUserIsLoggedIn();
}
codeception: page object
class Homepage extends FrontBasePage {
function open() {
$I = $this->tester;
$I->amOnUrl($this->app->url(':Front:Homepage:'));
}
codeception: page object
abstract class FrontBasePage {
function __construct(WebGuy $tester, NetteApp $app);
function login($email, $password) {
$form = new LoginForm($this->tester);
$form->openLoginDialog();
$form->login($email, $password);
}
codeception: page component
class LoginForm {
function __construct(WebGuy $tester);
function login($email, $password) {
$I = $this->tester;
$I->fillField(['name' => 'email'], $email);
$I->fillField(['name' => 'password'], $password);
$I->click(['css' => '.form-login form .login-btn button[type=submit]']);
}
codeception: nette app integration
class NetteApp {
function __construct(NetteSetup $netteSetup);
function url($fqa, $args = []);
function getService($type);
function getContainer();
Díky za pozornost!
Dotazy?
@ProchazkaFilip

Testování prakticky

  • 1.
  • 2.
    Co si povíme -troška teorie - nette/tester - mockery/mockery - testování proti síti - integrační testy - codeception/codeception - selenium
  • 3.
    Troška teorie - cotestovat - že to funguje - že to nefunguje (když nemá) - jak testovat - unit testy - integrační testy - akceptační testy - selenium (high level akceptační testy) - Test Driven Development (red, green, refactor) - Behaviour Driven Development
  • 5.
  • 6.
    tester: motivace $obj =new Math; $result = $obj->add(1, 2); var_dump($result);
  • 7.
    tester: motivace $obj =new Math; $result = $obj->add(1, 2); Assert::same(3, $result);
  • 8.
    tester: tests/bootstrap.php require __DIR__. '/../vendor/autoload.php'; TesterEnvironment::setup(); date_default_timezone_set('Europe/Prague');
  • 9.
    tester: tests/math/add.phpt require __DIR__. '/../bootstrap.php'; $obj = new Math; $result = $obj->add(1, 2); Assert::same(3, $result);
  • 10.
    tester: TesterAssert Assert::same($expected, $actual); Assert::equal($expected,$actual); Assert::contains($needle, $actual); Assert::true($value); Assert::false($value); Assert::type($type, $value); // ...
  • 11.
    tester: TesterAssert $obj =new Math(); Assert::exception(function() use ($obj) { $obj->divide(2, 0); }, InvalidArgumentException::class, 'Cannot divide by zero');
  • 12.
    tester: TestCase class SomeTestextends TesterTestCase { function setUp() { } function tearDown() { } function testOne() { Assert::same(......); } } (new SomeTest)->run();
  • 13.
    tester: TestCase @throws /** *@throws InvalidArgumentException */ public function testOne() { // ... }
  • 14.
    tester: TestCase @dataProvider publicfunction dataLoop() { return [ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]; } /** @dataProvider dataLoop */ public function testLoop($a, $b, $c) { // ... }
  • 15.
    tester: TestCase @dataProvider publicfunction dataLoop() { yield [1, 2, 3], yield [4, 5, 6], yield [7, 8, 9], } /** @dataProvider dataLoop */ public function testLoop($a, $b, $c) { // ... }
  • 16.
    tester: metadata <?php /** * @testCase */ require__DIR__ . '/../bootstrap.php'; class SomeTest extends TesterTestCase {
  • 17.
    _____ ___ ________ ___ ___ |_ _/ __)( __/_ _/ __)| _ ) |_| ___ /___) |_| ___ |_|_ v1.6.1 PHP 7.0.0 | 'php-cgi' -n | 10 threads ........s................F......... -- FAILED: tests/greeting.phpt Failed: 'Hello John' should be ... 'Hi John' in src/Framework/Assert.php(370) in src/Framework/Assert.php(52) TesterAssert::fail() in tests/greeting.phpt(6) TesterAssert::same() FAILURES! (35 tests, 1 failures, 1 skipped, 1.7 seconds)
  • 18.
    tester: a další... -data providery & multipliery (meta) - skipování testů - DomQuery na testování HTML & XML - file mock - zámky (aby se nemlátily paralelní testy) - watch (automatické spouštění testů při změně kódu) - generování code coverage - podpora HHVM
  • 19.
  • 20.
    mockery: testovaná třída classTemperature { function __construct(WeatherApi $weather); function average() { $total = 0; for ($i = 0; $i < 3 ;$i++) { $total += $this->weather->readTemp(); } return $total / 3; }
  • 21.
    mockery: test vTesteru class TemperatureTest extends TesterTestCase { function testGetsAverageTemperature() { $service = Mockery::mock(WeatherApi::class); $service->shouldReceive('readTemp') ->times(3)->andReturn(10, 12, 14); $temperature = new Temperature($service); Assert::same(12, $temperature->average()); }
  • 22.
    mockery: test vTesteru // ... function tearDown() { Mockery::close(); TesterEnvironment::$checkAssertions = FALSE; }
  • 23.
  • 24.
    Testování proti síti -je to pomalé - síť nemusí fungovat - formát/protocol se může změnit - služba může mít výpadky
  • 25.
    Testování proti síti try{ $result = $paymentProcessor->process($order); } catch (OrdersCardPaymentException $e) { if (preg_match('~SoapClient.*? Connection (refused|timed out)~', $e->getMessage())) { TesterEnvironment::skip($msg); } throw $e; }
  • 26.
    Testování proti síti -knihovna používá psr/http-message - výchozí je guzzle klient - testy používají fake klienta - api se reálně zavolá pouze poprvé - výsledek se uloží - další běhy načítají odpovědi z disku - když soubor smažu, request se provede Ukázka v testech Kdyby/CsobPaymentGateway na githubu
  • 27.
    Testování proti síti -můžu (alespoň částečně) vyvíjet offline - testy náhodně nepadají - testy jsou rychlé
  • 28.
  • 29.
    Integrační testy abstract classIntegrationTestCase extends TestCase { function getService($type); function getContainer() {} function createContainer() {} abstract class DbTestCase extends IntegrationTestCase { function createContainer() {} function setupDatabase(Connection $db);
  • 30.
    Integrační testy: databáze classCartTest extends IntegrationTestCase { function testAdd() { $cart = $this->getService(OrderCart::class); $cart->addItem(10); // položka s id 10 $itemsInDb = $this->getService(Connection::class) ->query("SELECT id FROM order_items WHERE order = 1"); Assert::same([10], $itemsInDb); }
  • 31.
    Integrační testy: presentery abstractclass PresenterTestCase extends DbTestCase { function usePresenter($name); function runPresenterAction($action, ...); function makeAjax(); function fakeCsrfProtection(); function fakeRedirectFromWebpay($action, ...); function fakeRedirectFromCsob($action, ...);
  • 32.
    Integrační testy: presentery classHomepagePresenterTest extends PresenterTestCase { function setUp() { parent::setUp(); $this->usePresenter('Front:Homepage'); } function testRenderDefault() { $response = $this->runPresenterAction('default'); Assert::type(TextResponse::class, $response); }
  • 33.
    Integrační testy: komponenty abstractclass ComponentTestCase extends DbTestCase { function attachToPresenter($component, ...); function loadState($params, ...); function runSignal($signal, $params, ...); function makeAjax(); function assertRedirect($url); function assertSnippets($snippets);
  • 34.
    Integrační testy: komponenty classCartControlTest extends ComponentTestCase { function testCopyItem() { $basket = $this->getService(Cart::class); $item = $basket->addItem(719693); $control = $this->getService(ICartControlFactory::class)->create(); $this->attachToPresenter($control); $this->runSignal('copyItem', ['item' => $item->getId()]); // ...
  • 35.
    Integrační testy: komponenty //... Assert::same(2, $item->getCount()); $this->assertSnippets([ 'snippet-user-panel', 'snippet-review', 'snippet-control-cart' => '%A%Milka Tender oříšková%A%' ]);
  • 36.
  • 38.
    codeception: tests/acceptance.suite.yml class_name: AcceptanceTester modules: enabled: -PhpBrowser: url: 'http://127.0.0.1:8000' - HelperAcceptance
  • 39.
    codeception: simple test $I= new AcceptanceTester($scenario); $I->wantTo('see note can be successfully created'); $I->testLogin(); $I->amOnPage('/notes/create'); $I->fillField('Name', 'Example note'); $I->fillField('Text', 'Lorem ipsum'); $I->selectOption('Pad', 1); $I->click('Save'); $I->see('Example note');
  • 40.
  • 41.
    codeception: tests/acceptance.suite.yml class_name: WebGuy modules: enabled: -CodeceptionModuleWebDriver - SeleniumTestsSupportHelperNetteSetup config: CodeceptionModuleWebDriver: url: 'http://www.rohlik.l/' browser: 'firefox'
  • 42.
    codeception: cest test classSigninCest { function loginWithPassword(Homepage $homepage) { $homepage->open(); $homepage->login('filip@prochazka.su', '12345678'); $homepage->seeThatUserIsLoggedIn(); }
  • 43.
    codeception: page object classHomepage extends FrontBasePage { function open() { $I = $this->tester; $I->amOnUrl($this->app->url(':Front:Homepage:')); }
  • 44.
    codeception: page object abstractclass FrontBasePage { function __construct(WebGuy $tester, NetteApp $app); function login($email, $password) { $form = new LoginForm($this->tester); $form->openLoginDialog(); $form->login($email, $password); }
  • 45.
    codeception: page component classLoginForm { function __construct(WebGuy $tester); function login($email, $password) { $I = $this->tester; $I->fillField(['name' => 'email'], $email); $I->fillField(['name' => 'password'], $password); $I->click(['css' => '.form-login form .login-btn button[type=submit]']); }
  • 46.
    codeception: nette appintegration class NetteApp { function __construct(NetteSetup $netteSetup); function url($fqa, $args = []); function getService($type); function getContainer();
  • 47.