My Top 10 PHPUnit Tips And Tricks

My Top 10 PHPUnit Tips And Tricks

Presented on June 26 2020 at the Dutch PHP Conference Online Edition.
https://schedule.phpconference.nl/talk/2
---------------------------------------------------------------
Of course you test your code... you may even use test driven development. But do those tests really add value ? Are your tests actually testing your code ? Or are they just there to satisfy the CI process ?

In this talk Juliette will focus on all the things she's learned in years of writing and reviewing tests, the pitfalls she fell into, and the tips and tricks she learned along the way.
---------------------------------------------------------------

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

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

Infection:
Docs: https://infection.github.io/
Demo: https://youtu.be/ADKyTlaH6e4

2776198ea9584b6c0d4b494293b8d635?s=128

Juliette Reinders Folmer

June 26, 2020
Tweet

Transcript

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

    Tweet about it: @jrf_nl #DPC20 Olivier Issaly
  2. Before we start...  Follow along with the code samples

    used: https://github.com/jrfnl/ top-10-phpunit-tips-tricks- demo  Discuss in Slack channel: #phpunit-tips-and-tricks davidjohn
  3. 0. Have Tests

  4. 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/"] } }
  5. 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> --generate-configuration
  6. Start Small (but start somewhere) <?php namespace PHPUnit_Demo; class Foo

    { static function stripQuotes($string) { return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string ); } } @562a010
  7. 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); } } @afda386
  8. 1. Use the Right Assertion

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

  10. Available Assertions

  11. 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); } } @ef6fa43
  12. 2. Don't Just Test the Happy Path

  13. <?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 @ef108e2
  14. Allow for Testing the Unhappy Path  strict_types  Parameter

    type declarations mensatic
  15. 3. Limit Assertions Per Test

  16. None
  17. None
  18. <?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); } Use Fail Messages @2ccceaf
  19. <?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 @2ccceaf
  20. None
  21. 4. Use Data Providers

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

    @dataProvider dataStripQuotes * * @param mixed $in Function input. * @param string $out Expected output. * * @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, ''], ]; } @2ccceaf
  23. Without Data Provider With Data Provider

  24. 5. Don't Use Your Own Code To Create Test Data

  25. 6. Name Your Test Cases

  26. None
  27. 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, ''], ]; } @13e218b
  28. None
  29. 7. Explore Filtering

  30. 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'
  31. None
  32. 8. Don't Trust Code Coverage But do examine it

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

    return preg_replace( '`^([\'"])(.*)\1$`Ds', '$2', $string ); } }
  34. 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> @1710ccb See also: feature/ update-config-for- phpunit-9.3
  35. 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); } @1710ccb
  36. Simplify { "scripts" : { "test": [ "vendor/bin/phpunit --no-coverage" ],

    "coverage": [ "vendor/bin/phpunit" ], "coverage-local": [ "vendor/bin/phpunit --coverage-html ./build/coverage-html" ] } } @1710ccb
  37. None
  38. 9. Don't Leave Without Saying Goodbye

  39. Conditionally Skipping Tests [1] public function testIsCountable($input, $expected) { if

    (\version_compare(\PHP_VERSION_ID, '70300', '<')) { return; } $this->assertSame($expected, myCountable($input)); } example/test-skipping
  40. Conditionally Skipping Tests [2] public function testIsCountable($input, $expected) { if

    (\version_compare(\PHP_VERSION_ID, '70300', '<')) { $this->markTestSkipped(); return; } $this->assertSame($expected, myCountable($input)); } example/test-skipping
  41. Conditionally Skipping Tests [2] public function testIsCountable($input, $expected) { if

    (\version_compare(\PHP_VERSION_ID, '70300', '<')) { $this->markTestSkipped( 'This test requires PHP 7.3.0+' ); } $this->assertSame($expected, myCountable($input)); } example/test-skipping
  42. Conditionally Skipping Tests [4] /** * @requires PHP >= 7.3.0

    */ public function testIsCountable($input, $expected) { if (\version_compare(\PHP_VERSION_ID, '70300', '<')) { $this->markTestSkipped( 'This test requires PHP 7.3.0+' ); return; } $this->assertSame($expected, myCountable($input)); } example/test-skipping
  43. 10. Make Your Tests Cross-Version Compatibility

  44. 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
  45. Cross-version Compatible Fixtures public static setupBeforeClass() : void { self::$db

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

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

  48. Test Your Tests  Infection https://infection.github.io/ https://youtu.be/ADKyTlaH6e4  PHPUnitCompatibility (upcoming)

     PHPUnitQA (upcoming) lisaleo
  49. 12. Lose Count

  50. Thanks! @jrf_nl @jrfnl Any questions ? Feedback: https://joind.in/talk/2d124 Slides: https://speakerdeck.com/jrf

    Code: https://github.com/jrfnl/ top-10-phpunit-tips-tricks-demo Docs: https://phpunit.readthedocs.io/