My Top 10 PHPUnit Tips And Tricks

My Top 10 PHPUnit Tips And Tricks

Presented on February 26 2020 at the Confoo conference, Montreal, Canada.
https://confoo.ca/en/yul2020/session/my-top-10-phpunit-tips-tricks
---------------------------------------------------------------
Of course you test your code... you may even use test driven development.
But are your tests actually testing your code ? Or are they just there to satisfy the CI process ?

In this talk Juliette will focus on the tips and tricks she learned over the years.

Learn how to improve your test setup, what assertions to use when, why data providers are incredibly useful and how to make your tests more descriptive and easier to debug.
---------------------------------------------------------------

Links:
Code used in the presentation:
https://github.com/jrfnl/top-10-phpunit-tips-tricks-demo

PHPUnit Docs:
https://phpunit.readthedocs.io/

2776198ea9584b6c0d4b494293b8d635?s=128

Juliette Reinders Folmer

February 26, 2020
Tweet

Transcript

  1. My Top 10 PHPUnit Tips & Tricks Juliette Reinders Folmer

    Tweet about it: @jrf_nl #Confoo20 Olivier Issaly
  2. 0. Have Tests

  3. Have the basic setup in place [1] In composer.json: {

    "require-dev" : { "phpunit/phpunit": "^8.0 || ^9.0" }, "autoload": { "classmap": ["src/"] }, "autoload-dev": { "classmap": ["tests/"] } }
  4. Have the basic setup in place [2] In phpunit.xml.dist: <?xml

    version="1.0" encoding="UTF-8"?> <phpunit bootstrap="./vendor/autoload.php" colors="true" > <testsuites> <testsuite name="Foo"> <directory suffix="Test.php"> ./tests/ </directory> </testsuite> </testsuites> </phpunit>
  5. Start Small (but start somewhere) <?php namespace PHPUnit_Demo; class Foo

    { static function stripQuotes($string) { return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string ); } }
  6. Start Small (but start somewhere) <?php namespace PHPUnit_Demo\Tests; use PHPUnit\Framework\TestCase;

    use PHPUnit_Demo\Foo; class FooTest extends TestCase { public function testStripQuotes() { $result = Foo::stripQuotes('"text"'); $this->assertEquals('text', $result); } }
  7. 1. Use the Right Assertion

  8. assertEquals() == assertSame() === Most Common Issue

  9. Available Assertions

  10. Use the Right Assertion <?php namespace PHPUnit_Demo\Tests; use PHPUnit\Framework\TestCase; use

    PHPUnit_Demo\Foo; class FooTest extends TestCase { public function testStripQuotes() { $result = Foo::stripQuotes('"text"'); $this->assertEquals('text', $result); $this->assertSame('text', $result); } }
  11. 2. Don't Just Test the Happy Path

  12. <?php public function testStripQuotes() { $result = Foo::stripQuotes('"some text"'); $this->assertSame('some

    text', $result); $result = Foo::stripQuotes("some 'text'"); $this->assertSame("some 'text'", $result); $result = Foo::stripQuotes(false); $this->assertSame('', $result); } Don't Just Test the Happy Path
  13. Allow for Testing the Unhappy Path  strict_types  Parameter

    type declarations mensatic
  14. 3. Limit Assertions Per Test

  15. None
  16. None
  17. <?php public function testStripQuotes() { $result = Foo::stripQuotes('"some text"'); $this->assertSame('some

    text', $result, 'stripping quotes failed'); $result = Foo::stripQuotes("some 'text'"); $this->assertSame("some 'text'", $result, 'failed with quotes in string'); $result = Foo::stripQuotes(false); $this->assertSame('', $result, 'failed with non-string input'); } Use Fail Messages
  18. None
  19. 4. Use Data Providers

  20. Test Method Data Provider /** * Test Foo::stripQuotes(). * *

    @dataProvider dataStripQuotes * * @param string $in * @param string $out * * @return void */ public function testStripQ($in, $out) { $result = Foo::stripQuotes($in); $this->assertSame($out, $result); } /** * Data provider. * * @return array[] */ public function dataStripQuotes() { return [ ['"some text"', 'some text'], ["some 'text'", "some 'text'"], [false, ''], ]; }
  21. 5. Don't Use Your Own Code To Create Test Data

  22. 6. Name Your Test Cases

  23. None
  24. Named Test Cases /** * Data provider for the testStripQuotes()

    test. * * @return array[] */ public function dataStripQuotes() { return [ 'double quoted text' => ['"some text"', 'some text'], 'quotes with quoted text within' => ["some 'text'", "some 'text'"], 'not string input - bool' => [false, ''], ]; }
  25. None
  26. 7. Explore Filtering

  27. Filter Options Filter Shortcuts --filter TestNamespace --filter TestClass --filter testMethod

    --filter 'TestNS\\TClass::testMethod' --filter 'TestNS\\TestClass' --filter '/::testMethod .*"name"/' --filter '/::testMethod .*#5$/' --filter '/::testMethod .*#(1|2|5)$/' --filter 'testMethod#5' --filter 'testMethod#4-6' --filter '#1' --filter '#1-3' --filter 'testMethod@named test case' --filter 'testMethod@name.*case' --filter '@named test case' --filter '@named.*case'
  28. None
  29. 8. Don't Trust Code Coverage But do examine it

  30. <?php namespace PHPUnit_Demo; class Foo { static function stripQuotes($string) {

    return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string ); } }
  31. Enabling Code Coverage [1] <?xml version="1.0" encoding="UTF-8"?> <phpunit beStrictAboutCoversAnnotation="true" forceCoversAnnotation="true">

    <filter> <whitelist addUncoveredFilesFromWhitelist="true"> <directory suffix=".php">src</directory> </whitelist> </filter> <logging> <log type="coverage-clover" target="build/logs/clover.xml"/> </logging> </phpunit>
  32. Enabling Code Coverage [2] <?php class FooTest extends TestCase {

    /** * Test Foo::stripQuotes(). * * @dataProvider dataStripQuotes * * @covers \PHPUnit_Demo\Foo::stripQuotes */ public function testStripQuotes($in, $out) { $result = Foo::stripQuotes($input); $this->assertSame($expected, $result); }
  33. Simplify { "scripts" : { "test": [ "vendor/bin/phpunit --no-coverage" ],

    "coverage": [ "vendor/bin/phpunit" ], "coverage-local": [ "vendor/bin/phpunit --coverage- html ./build/coverage-html" ] } }
  34. None
  35. 9. Don't Leave Without Saying Goodbye

  36. public function testStripQuotes($input, $expected) { if (\version_compare(\PHP_VERSION_ID, '70300', '<')) {

    return; } $this->assertSame($expected, $result); }
  37. public function testStripQuotes($input, $expected) { if (\version_compare(\PHP_VERSION_ID, '70300', '<')) {

    $this->markTestSkipped(); return; } $this->assertSame($expected, $result); }
  38. public function testStripQuotes($input, $expected) { if (\version_compare(\PHP_VERSION_ID, '70300', '<')) {

    $this->markTestSkipped( 'This test requires PHP 7.3.0+' ); return; } $this->assertSame($expected, $result); }
  39. /** * @requires PHP >= 7.3.0 */ public function testStripQuotes($input,

    $expected) { if (\version_compare(\PHP_VERSION_ID, '70300', '<')) { $this->markTestSkipped( 'This test requires PHP 7.3.0+' ); return; } $this->assertSame($expected, $result); }
  40. 10. Make Your Tests Cross-Version Compatibility

  41. PHPUnit Support v Compatible with: 9 PHP 7.3, 7.4 (support

    ends Feb 2022) 8 PHP 7.2, 7.3, 7.4 (support ends Feb 2021) 7 PHP 7.1, 7.2, 7.3 6 PHP 7.0, 7.1, 7.2 5 PHP 5.6, 7.0, 7.1 4 PHP 5.3, 5.4, 5.5, 5.6 Seemann
  42. Cross-version Compatible Fixtures public static setupBeforeClass() : void { self::$db

    = new DBConnector(); } public static tearDownAfterClass() : void { self::$db->disconnect(); }
  43. Cross-version Compatible Fixtures /** * @beforeClass */ public static setupBeforeClass()

    : void { public static setupDBConnection() { self::$db = new DBConnector(); } /** * @afterClass */ public static tearDownAfterClass() : void { public static disconnectDB() : void { self::$db->disconnect(); unset(self::$db); }
  44. 11. Test Your Tests

  45. Test Your Tests  Infection  PHPUnitCompatibility (upcoming)  PHPUnitQA

    (upcoming) lisaleo
  46. 12. Lose Count

  47. Thanks! @jrf_nl @jrfnl Any questions ? Slides: https://speakerdeck.com/jrf Code: https://github.com/jrfnl/

    top-10-phpunit-tips-tricks-demo Docs: https://phpunit.readthedocs.io/ Feedback: https://joind.in/talk/dafa1