Richtig testen
Sebastian Bergmann
21. Februar 2019 | München
Sebastian Bergmann
Interessengemeinscha� PHP e.V.
Als gemeinnütziger Verein möchten wir die Akzeptanz, Verbreitung und
Weiterentwicklung der Programmiersprache PHP und das Wissen um die
Programmiersprache PHP in Wissenscha�, Forschung und Ausbildung
�ördern.
https://igphp.de/
@igphp
Richtig testen
21.02.2019 | Sebastian Bergmann
Die richtige So�ware entwickeln. Das Richtige testen. Zum richtigen Zeitpunkt.
Mit dem passenden Werkzeug sowie den richtigen Kni�en, wie man es e�ektiv und e�zient
einsetzt. In diesem Vortrag gehen wir unter anderem den folgenden Fragen nach:
Was will ich testen? In welchem Rahmen muss ich es testen? Wie formuliere ich den Test?
https://thephp.cc/termine/2019/02/tech-and-drinks-at-myposter/richtig-testen
Richtig testen
21.02.2019 | Sebastian Bergmann
Die richtige So�ware entwickeln. Das Richtige testen. Zum richtigen Zeitpunkt.
Mit dem passenden Werkzeug sowie den richtigen Kni�en, wie man es e�ektiv und e�zient
einsetzt. In diesem Vortrag gehen wir unter anderem den folgenden Fragen nach:
Was will ich testen? In welchem Rahmen muss ich es testen? Wie formuliere ich den Test?
https://thephp.cc/termine/2019/02/tech-and-drinks-at-myposter/richtig-testen
Die �ünf W des Testens
Was wollen wir mit dem Test erreichen?
Was wollen wir mit dem Test erreichen?
Wieviel Code müssen wir aus�ühren?
Was wollen wir mit dem Test erreichen?
Wieviel Code müssen wir aus�ühren?
Wie formulieren wir den Test?
Was wollen wir mit dem Test erreichen?
Wieviel Code müssen wir aus�ühren?
Wie formulieren wir den Test?
Wann schreiben wir den Test?
Was wollen wir mit dem Test erreichen?
Wieviel Code müssen wir aus�ühren?
Wie formulieren wir den Test?
Wann schreiben wir den Test?
Wann �ühren wir den Test aus?
Unit Test
Unit Test
Funktionieren die kleinsten Einheiten der So�ware isoliert
voneinander?
Unit Test
Funktionieren die kleinsten Einheiten der So�ware isoliert
voneinander?
Sind die kleinsten Einheiten der So�ware einfach zu testen?
Unit Test
Funktionieren die kleinsten Einheiten der So�ware isoliert
voneinander?
Sind die kleinsten Einheiten der So�ware einfach zu testen?
Sind die kleinsten Einheiten der So�ware einfach zu verwenden?
Unit Test
Funktionieren die kleinsten Einheiten der So�ware isoliert
voneinander?
Sind die kleinsten Einheiten der So�ware einfach zu testen?
Sind die kleinsten Einheiten der So�ware einfach zu verwenden?
Ist die So�ware handwerklich sauber implementiert?
"Sauberer Code kann von anderen Entwicklern gelesen und verbessert
werden. Er ver�ügt über Unit- und Acceptance-Tests. Er enthält
bedeutungsvolle Namen. Er stellt zur Lösung einer Aufgabe nicht
mehrere, sondern eine Lösung zur Ver�ügung. Er enthält minimale
Abhängigkeiten, die ausdrücklich de�niert sind, und stellt ein klares
und minimales API zur Ver�ügung."
— Dave Thomas
"For a class to be easy to unit-test, the class must have explicit
dependencies that can easily be substituted and clear responsibilities
that can easily be invoked and veri�ed. In so�ware-engineering
terms, that means that the code must be loosely coupled and highly
cohesive — in other words, well-designed."
— Steve Freeman und Nat Pryce
Unit Test
public function testIsInitiallyEmpty(): void{ $cart = new ShoppingCart;
$this->assertEmpty($cart->items());}
Unit Test
public function testTotalForEmptyShoppingCartIs0(): void{ $cart = new ShoppingCart;
$this->assertEquals(EUR::fromCents(0), $cart->total());}
Unit Test
public function testItemCanBeAdded(): void{ $item = $this->createMock(ShoppingCartItem::class);
$cart = new ShoppingCart; $cart->addItem($item);
$this->assertCount(1, $cart->items()); $this->assertContains($item, $cart->items());}
Integrationstest
Integrationstest
Funktioniert die Kommunikation zwischen den Komponenten?
Integrationstest
Funktioniert die Kommunikation zwischen den Komponenten?
Funktioniert die Kommunikation mit anderen Systemen?
Integrationstest
Funktioniert die Kommunikation zwischen den Komponenten?
Funktioniert die Kommunikation mit anderen Systemen?
Ist die So�ware richtig integriert?
Akzeptanztest
Akzeptanztest
Er�üllt die So�ware die Akzeptanzkriterien?
Akzeptanztest
Er�üllt die So�ware die Akzeptanzkriterien?
Funktioniert die So�ware als Ganzes?
Akzeptanztest (End-to-End)
public function testShoppingCartPageCanBeDisplayed(): void{ $page = $this->visit('https://my.shop/cart');
$this->assertCount(1, $page->findAll('css', '.cart_item')); $this->assertEquals('Foo', $page->find('css', '.cart_item_name')->getHtml()); $this->assertEquals('1,23 EUR', $page->find('css', '.cart_item_unit_price')->getHtml()); $this->assertEquals('2,46 EUR', $page->find('css', '.cart_item_total_price')->getHtml());}
Akzeptanztest (Edge-to-Edge)
public function testShoppingCartPageCanBeDisplayed(): void{ $_SERVER['REQUEST_METHOD'] = 'GET'; $_SERVER['REQUEST_URI'] = '/cart'; $_GET = [];
$factory = new Factory; $application = $factory->createApplication(); $request = Request::fromSuperGlobals(); $response = $application->run($request); $content = $response->getContent();
$this->assertShoppingCartItemCount(1, $content); $this->assertShoppingCartTotal('1,23 EUR', $content);
// ...}
Akzeptanztest (Edge-to-Edge)
public function testShoppingCartPageCanBeDisplayed(): void{ $this->getRequest('/cart');
$content = $this->run();
$this->assertShoppingCartItemCount(1, $content); $this->assertShoppingCartTotal('1,23 EUR', $content);
// ...}
https://thephp.cc/termine/2017/11/php-ruhr/domain-speci�c-assertions
https://www.youtube.com/watch?v=FC7Sz8gwbuc
Systemtest
Systemtest
Sind die Qualitätsziele erreicht?
"�unning end-to-end tests tells us about the the external quality of
our system, and writing them tells us something about how well we
[. . .] understand the domain, but end-to-end tests don't tell us how
well we've written the code. Writing unit tests gives us a lot of
feedback about the quality of our code, and running them tell us that
we haven't broken any classes [. . .]"
— Steve Freeman und Nat Pryce
Richtig testen
21.02.2019 | Sebastian Bergmann
Die richtige So�ware entwickeln. Das Richtige testen. Zum richtigen Zeitpunkt.
Mit dem passenden Werkzeug sowie den richtigen Kni�en, wie man es e�ektiv und e�zient
einsetzt. In diesem Vortrag gehen wir unter anderem den folgenden Fragen nach:
Was will ich testen? In welchem Rahmen muss ich es testen? Wie formuliere ich den Test?
https://thephp.cc/termine/2019/02/tech-and-drinks-at-myposter/richtig-testen
Richtig testen
21.02.2019 | Sebastian Bergmann
Die richtige So�ware entwickeln. Das Richtige testen. Zum richtigen Zeitpunkt.
Mit dem passenden Werkzeug sowie den richtigen Kni�en, wie man es e�ektiv und e�zient
einsetzt. In diesem Vortrag gehen wir unter anderem den folgenden Fragen nach:
Was will ich testen? In welchem Rahmen muss ich es testen? Wie formuliere ich den Test?
https://thephp.cc/termine/2019/02/tech-and-drinks-at-myposter/richtig-testen
$ phive install --copy phpunit Phive 0.11.0 - Copyright (C) 2015-2019 by Arne Blankerts, Sebastian Heuer and Contributors Copying phpunit-8.0.4.phar to /home/sb/project/tools/phpunit $ ./tools/phpunit --version PHPUnit 8.0.4 by Sebastian Bergmann and contributors.
PHA�? Phive? Warum nicht Composer?
https://twitter.com/s_bergmann/status/999635212723212288
$ tree . ├── phive.xml ├── src │ ├── autoload.php │ ├── BillingAddress.php │ ├── CreditCard.php │ ├── Email.php │ ├── ShippingAddress.php │ ├── ShoppingCartItem.php │ ├── ShoppingCart.php │ └── ShoppingCartRepository.php ├── tests │ ├── acceptance │ │ └── CheckoutProcessTest.php │ ├── integration │ │ └── ShoppingCartRepositoryTest.php │ └── unit │ └── ShoppingCartTest.php └── tools └── phpunit 6 directories, 13 files
$ ./tools/phpunit --generate-configuration PHPUnit 8.0.4 by Sebastian Bergmann and contributors. Generating phpunit.xml in /home/sb/project Bootstrap script (relative to path shown above; default: vendor/autoload.php): src/autoload.php Tests directory (relative to path shown above; default: tests): Source directory (relative to path shown above; default: src): Generated phpunit.xml in /home/sb/project
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="default"> <directory suffix="Test.php">tests</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?><phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/8.0/phpunit.xsd" bootstrap="src/autoload.php" executionOrder="depends,defects" forceCoversAnnotation="true" beStrictAboutCoversAnnotation="true" beStrictAboutOutputDuringTests="true" beStrictAboutTodoAnnotatedTests="true" verbose="true"> <testsuites> <testsuite name="unit"> <directory suffix="Test.php">tests/unit</directory> </testsuite> <testsuite name="integration"> <directory suffix="Test.php">tests/integration</directory> </testsuite> <testsuite name="acceptance"> <directory suffix="Test.php">tests/acceptance</directory> </testsuite> </testsuites>
<filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter></phpunit>
$ ./tools/phpunit PHPUnit 8.0.4 by Sebastian Bergmann and contributors. Runtime: PHP 7.3.2 with Xdebug 2.7.0RC2 Configuration: /home/sb/project/phpunit.xml ........... 11 / 11 (100%) Time: 83 ms, Memory: 12.00MB OK (11 tests, 11 assertions)
$ ./tools/phpunit --testsuite unit PHPUnit 8.0.4 by Sebastian Bergmann and contributors. Runtime: PHP 7.3.2 with Xdebug 2.7.0RC2 Configuration: /home/sb/project/phpunit.xml ... 3 / 3 (100%) Time: 69 ms, Memory: 10.00MB OK (3 tests, 3 assertions)
$ ./tools/phpunit --testdox PHPUnit 8.0.4 by Sebastian Bergmann and contributors. Runtime: PHP 7.3.2 with Xdebug 2.7.0RC2 Configuration: /home/sb/project/phpunit.xml ShoppingCart ✔ Is initially empty [0.15 ms] ✔ Total for empty shopping cart is 0 [0.16 ms] ✔ Item can be added [0.15 ms] ShoppingCartRepository ✔ Can save shopping cart [0.17 ms] ✔ Can find shopping cart by id [0.15 ms] CheckoutProcess ✔ Item can be added to shopping cart [0.32 ms] ✔ Shopping cart page can be displayed [0.16 ms] ✔ Shipping address can be entered [0.16 ms] ✔ Billing address can be entered [0.16 ms] ✔ Payment information can be entered [0.16 ms] ✔ Purchase can be completed [0.16 ms] Time: 83 ms, Memory: 12.00MB OK (11 tests, 11 assertions)
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class RouterTest extends TestCase{ /** * @dataProvider routesProvider */ public function testRoutesRequest(string $url, string $handler): void { // ... }
public function routesProvider(): array { return [ '/foo/bar' => [ '/foo/bar', FooBarHandler::class, // ... ] ]; }}
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class RouterTest extends TestCase{ /** * @dataProvider routesProvider */ public function testRoutesRequest(string $url, string $handler): void { // ... }
public function routesProvider(): array { return [ '/foo/bar' => [ '/foo/bar', FooBarHandler::class, // ... ] ]; }}
Router ✔ Routes request data set "/foo/bar"
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class RouterTest extends TestCase{ /** * @dataProvider routesProvider * @testdox Routes $url to $handler */ public function testRoutesRequest(string $url, string $handler): void { // ... }
public function routesProvider() { return [ '/foo/bar' => [ '/foo/bar', FooBarHandler::class, // ... ] ]; }}
Router ✔ Routes /foo/bar to FooBarHandler
https://thephp.cc/neuigkeiten/2019/01/faster-code-coverage
https://blog.krakjoe.ninja/2019/01/running-for-coverage.html
Code Coverage Performance
Code Coverage Time Memory
PHP 7.3.2 22 seconds 81 MB
PHP 7.3.2 + Xdebug 2.7.0�C2 3 minutes 81 MB
PHP 7.3.2 + Xdebug 2.7.0�C2 ✓ 16 minutes 97 MB
PHP 7.3.2 + Xdebug 2.7.0�C2 with native �ltering ✓ 15 minutes 95 MB
PHPDBG 7.3.2 ✓ 3 minutes 7.5 GB
PHP 7.3.2 + PCOV 1.0.0 ✓ 1.5 minutes 3.8 GB
Lies, damn lies, and benchmarks
$ git clone https://github.com/sebastianbergmann/diff.git $ cd diff $ composer install $ ./vendor/bin/phpunit
Kontakt
https://thephp.cc
https://talks.thephp.cc
@s_bergmann
Top Related