$30 off During Our Annual Pro Sale. View Details »

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

Juliette Reinders Folmer

June 26, 2020
Tweet

More Decks by Juliette Reinders Folmer

Other Decks in Programming

Transcript

  1. My Top 10 PHPUnit
    Tips & Tricks
    Juliette Reinders Folmer Tweet about it: @jrf_nl #DPC20
    Olivier Issaly

    View Slide

  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

    View Slide

  3. 0.
    Have Tests

    View Slide

  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/"]
    }
    }

    View Slide

  5. Have the basic
    setup in place [2]
    In phpunit.xml.dist:

    bootstrap="./vendor/autoload.php"
    colors="true"
    >



    ./tests/




    --generate-configuration

    View Slide

  6. Start Small
    (but start somewhere)
    namespace PHPUnit_Demo;
    class Foo
    {
    static function stripQuotes($string)
    {
    return preg_replace(
    '`^([\'"])(.*)\1$`Ds',
    '$2',
    $string
    );
    }
    }
    @562a010

    View Slide

  7. Start Small
    (but start somewhere)
    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

    View Slide

  8. 1.
    Use the Right Assertion

    View Slide

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

    View Slide

  10. Available Assertions

    View Slide

  11. Use the
    Right Assertion
    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

    View Slide

  12. 2.
    Don't Just Test
    the Happy Path

    View Slide

  13. 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

    View Slide

  14. Allow for Testing the
    Unhappy Path
     strict_types
     Parameter type
    declarations
    mensatic

    View Slide

  15. 3.
    Limit Assertions
    Per Test

    View Slide

  16. View Slide

  17. View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. View Slide

  21. 4.
    Use Data Providers

    View Slide

  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

    View Slide

  23. Without Data Provider
    With Data Provider

    View Slide

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

    View Slide

  25. 6.
    Name Your Test Cases

    View Slide

  26. View Slide

  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

    View Slide

  28. View Slide

  29. 7.
    Explore Filtering

    View Slide

  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'

    View Slide

  31. View Slide

  32. 8.
    Don't Trust Code Coverage
    But do examine it

    View Slide

  33. namespace PHPUnit_Demo;
    class Foo
    {
    static function stripQuotes($string)
    {
    return preg_replace(
    '`^([\'"])(.*)\1$`Ds',
    '$2',
    $string
    );
    }
    }

    View Slide

  34. Enabling Code
    Coverage [1]

    beStrictAboutCoversAnnotation="true"
    forceCoversAnnotation="true"
    >
    ...

    addUncoveredFilesFromWhitelist="true">
    src



    target="build/logs/clover.xml"/>


    @1710ccb
    See also:
    feature/
    update-config-for-
    phpunit-9.3

    View Slide

  35. Enabling Code
    Coverage [2]
    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

    View Slide

  36. Simplify
    {
    "scripts" : {
    "test": [
    "vendor/bin/phpunit --no-coverage"
    ],
    "coverage": [
    "vendor/bin/phpunit"
    ],
    "coverage-local": [
    "vendor/bin/phpunit
    --coverage-html ./build/coverage-html"
    ]
    }
    }
    @1710ccb

    View Slide

  37. View Slide

  38. 9.
    Don't Leave Without
    Saying Goodbye

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  43. 10.
    Make Your Tests
    Cross-Version Compatibility

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  47. 11.
    Test Your Tests

    View Slide

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

    View Slide

  49. 12.
    Lose Count

    View Slide

  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/

    View Slide