Post on 20-Mar-2017
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('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