+ All Categories
Home > Software > Filip Procházka - Testování prakticky

Filip Procházka - Testování prakticky

Date post: 20-Mar-2017
Category:
Upload: konference-phplive
View: 387 times
Download: 3 times
Share this document with a friend
47
Testování prakticky @ProchazkaFilip
Transcript

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';

Tester\Environment::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: Tester\Assert

Assert::same($expected, $actual);

Assert::equal($expected, $actual);

Assert::contains($needle, $actual);

Assert::true($value);

Assert::false($value);

Assert::type($type, $value);

// ...

tester: Tester\Assert

$obj = new Math();

Assert::exception(function() use ($obj) {

$obj->divide(2, 0);

}, \InvalidArgumentException::class, 'Cannot divide by zero');

tester: TestCaseclass SomeTest extends Tester\TestCase {

function setUp() { }

function tearDown() { }

function testOne() {

Assert::same(......);

}

}

(new SomeTest)->run();

tester: TestCase @throws

/**

* @throws InvalidArgumentException

*/

public function testOne()

{

// ...

}

tester: TestCase @dataProviderpublic function dataLoop() {

return [

[1, 2, 3],

[4, 5, 6],

[7, 8, 9],

];

}

/** @dataProvider dataLoop */

public function testLoop($a, $b, $c) {

// ...

}

tester: TestCase @dataProviderpublic 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 Tester\TestCase {

_____ ___ ___ _____ ___ ___|_ _/ __)( __/_ _/ __)| _ ) |_| \___ /___) |_| \___ |_|_\ 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) Tester\Assert::fail() in tests/greeting.phpt(6) Tester\Assert::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řídaclass 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 Testeruclass TemperatureTest extends Tester\TestCase {

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();

\Tester\Environment::$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 (Orders\CardPaymentException $e) {

if (preg_match('~SoapClient.*? Connection (refused|timed out)~', $e->getMessage())) {

Tester\Environment::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í testyabstract 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ázeclass 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: presenteryabstract class PresenterTestCase extends DbTestCase {

function usePresenter($name);

function runPresenterAction($action, ...);

function makeAjax();

function fakeCsrfProtection();

function fakeRedirectFromWebpay($action, ...);

function fakeRedirectFromCsob($action, ...);

Integrační testy: presenteryclass 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: komponentyabstract 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: komponentyclass 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'

- \Helper\Acceptance

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.ymlclass_name: WebGuy

modules:

enabled:

- Codeception\Module\WebDriver

- SeleniumTests\Support\Helper\NetteSetup

config:

Codeception\Module\WebDriver:

url: 'http://www.rohlik.l/'

browser: 'firefox'

codeception: cest test

class SigninCest {

function loginWithPassword(Homepage $homepage) {

$homepage->open();

$homepage->login('[email protected]', '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


Recommended