Successfully reported this slideshow.
Your SlideShare is downloading. ×

Leveling Up With Unit Testing - LonghornPHP 2022

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 72 Ad

Leveling Up With Unit Testing - LonghornPHP 2022

Download to read offline

Writing unit testing on a project can seem like a daunting task, and earning team and leadership buy-in can be challenging. Level up your skillset as we cover PHPUnit and Prophecy setup with composer, writing meaningful tests, restructuring existing classes with dependency injection to allow for unit testing, using mock objects, and releasing code confidently with test coverage. We'll also discuss overcoming common biases, unit testing challenges, and shortcomings of unit testing.

Writing unit testing on a project can seem like a daunting task, and earning team and leadership buy-in can be challenging. Level up your skillset as we cover PHPUnit and Prophecy setup with composer, writing meaningful tests, restructuring existing classes with dependency injection to allow for unit testing, using mock objects, and releasing code confidently with test coverage. We'll also discuss overcoming common biases, unit testing challenges, and shortcomings of unit testing.

Advertisement
Advertisement

More Related Content

Similar to Leveling Up With Unit Testing - LonghornPHP 2022 (20)

More from Mark Niebergall (20)

Advertisement

Recently uploaded (20)

Leveling Up With Unit Testing - LonghornPHP 2022

  1. 1. Leveling Up With Unit Testing Mark Niebergall https://joind.in/talk/906c6
  2. 2. 👏 Thank You! • LonghornPHP Organizers, Sponsors - Nucleus Security
  3. 3. https://analyze.co.za/wp-content/uploads/2018/12/441-1170x500.jpg
  4. 4. ✔ Objective ✓ Be familiar with how to setup PHPUnit ✓ Familiar with how to test existing code ✓ Know how to write unit tests using PHPUnit with Prophecy, Mockery ✓ Convince team and management to leverage automated testing
  5. 5. 👀 Overview • 😀 Bene fi ts of Unit Testing • ⚙ PHPUnit Setup • 🧑💻 Writing Unit Tests • ⌨ Testing Existing Code
  6. 6. 😀 Bene fi ts of Unit Testing
  7. 7. 😀 Bene fi ts of Unit Testing public static function add($a, $b) { return $a + $b; }
  8. 8. 😀 Bene fi ts of Unit Testing public static function add($a, $b) { return $a + $b; } public function add(float ...$numbers): float { $return = 0; foreach ($numbers as $value) { $return = bcadd( (string) $return, (string) $value, 10 ); } return (float) $return; }
  9. 9. 😀 Bene fi ts of Unit Testing http://www.ambysoft.com/artwork/comparingTechniques.jpg
  10. 10. 😀 Bene fi ts of Unit Testing • 💻 Automated way to test code - Regression Testing
  11. 11. 😀 Bene fi ts of Unit Testing • 💻 Automated way to test code - Continuous Integration (CI) - Continuous Deployment (CD)
  12. 12. 😀 Bene fi ts of Unit Testing • 💻 Automated way to test code - Other ways to automatically test code ‣ Behavioral (BDD): behat, phpspec ‣ Functional ‣ Acceptance: Selenium ‣ Others?
  13. 13. 😀 Bene fi ts of Unit Testing • 🪲 Decrease bugs introduced with code - Decreased time to deployment - Better use of QA team time
  14. 14. 😀 Bene fi ts of Unit Testing • 🪲 Decrease bugs introduced with code - High con fi dence in delivered code
  15. 15. 😀 Bene fi ts of Unit Testing • 💯 Con fi dence when refactoring - Tests covering code being refactored - TDD ‣ Change tests ‣ Tests fail ‣ Change code ‣ Tests pass
  16. 16. ⚙ PHPUnit Setup
  17. 17. ⚙ PHPUnit Setup • Install via composer • Setup `phpunit.xml` for con fi guration (if needed) • Run unit tests
  18. 18. ⚙ PHPUnit Setup • phpunit/phpunit • phpspec/prophecy-phpunit • mockery/mockery • fakerphp/faker
  19. 19. ⚙ PHPUnit Setup composer require --dev phpunit/phpunit composer require --dev phpspec/prophecy-phpunit composer require --dev mockery/mockery composer require --dev fakerphp/faker
  20. 20. ⚙ PHPUnit Setup • File phpunit.xml - PHPUnit con fi guration for that project - Documentation: https://phpunit.readthedocs.io/en/9.5/ con fi guration.html <?xml version="1.0" encoding="UTF-8"?> <phpunit colors="true" verbose="true" bootstrap="./tests/Bootstrap.php"> <testsuite name="All Tests"> <directory>./tests</directory> </testsuite> </phpunit>
  21. 21. ⚙ PHPUnit Setup • 💻 Running PHPUnit vendor/bin/phpunit tests/
  22. 22. ⚙ PHPUnit Setup • 💻 Running PHPUnit vendor/bin/phpunit tests/ PHPUnit 9.5.25 by Sebastian Bergmann and contributors. Runtime: PHP 8.1.10 Con fi guration: /Users/mniebergall/projects/phpunit/phpunit.xml ........... 11 / 11 (100%) Time: 00:00.025, Memory: 8.00 MB OK (11 tests, 15 assertions)
  23. 23. ⚙ PHPUnit Setup • 💻 Running PHPUnit - Within PhpStorm - https://www.jetbrains.com/help/phpstorm/using- phpunit-framework.html
  24. 24. ⚙ PHPUnit Setup • 📂 Directory Structure - PHP fi les in src/ ‣ Ex: src/Math/Adder.php - tests in tests/src/, ‘Test’ at end of fi lename ‣ Ex: tests/src/Math/AdderTest.php
  25. 25. 🧑💻 Writing Unit Tests
  26. 26. 🧑💻 Writing Unit Tests public function add(float ...$numbers): float { $return = 0; foreach ($numbers as $value) { $return = bcadd( (string) $return, (string) $value, 10 ); } return (float) $return; }
  27. 27. 🧑💻 Writing Unit Tests use PHPUnitFrameworkTestCase; class AdderTest extends TestCase { protected Adder $adder; public function setUp(): void { $this->adder = new Adder(); } public function testAdderWithSetup() { $sum = $this->adder->add(3, 7); $this->assertSame(10.0, $sum); }
  28. 28. 🧑💻 Writing Unit Tests public function testAdderThrowsExceptionWhenNotANumber() { $this->expectException(TypeError::class); $adder = new Adder(); $adder->add(7, 'Can't add this'); }
  29. 29. 🧑💻 Writing Unit Tests public function testAdderAddsIntegers() { $adder = new Adder(); $sum = $adder->add(7, 3, 5, 5, 6, 4, 1, 9); $this->assertSame(40.0, $sum); } public function testAdderAddsDecimals() { $adder = new Adder(); $sum = $adder->add(1.5, 0.5); $this->assertSame(2.0, $sum); }
  30. 30. 🧑💻 Writing Unit Tests /** * @dataProvider dataProviderNumbers */ public function testAdderAddsNumbers( float $expectedSum, ...$numbers ) { $adder = new Adder(); $sum = $adder->add(...$numbers); $this->assertSame($expectedSum, $sum); } public function dataProviderNumbers(): array { return [ [2, 1, 1], [2, 1.5, 0.5], ]; }
  31. 31. 🧑💻 Writing Unit Tests /** * @dataProvider dataProviderNumbers */ public function testAdderAddsNumbers( float $expectedSum, ...$numbers ) { $adder = new Adder(); $sum = $adder->add(...$numbers); $this->assertSame($expectedSum, $sum); } public function dataProviderNumbers(): iterable { yield 'integer' => [2, 1, 1]; yield 'integers with decimals' => [2, 1.5, 0.5]; }
  32. 32. 🧑💻 Writing Unit Tests • 📏 Test Coverage - Percent of code covered by tests - Not aiming for 100% - No need to test language constructs
  33. 33. 🧑💻 Writing Unit Tests • ⛔ Self-contained - No actual database connections - No API calls should occur - No external code should be called ‣ Use testing framework
  34. 34. 🧑💻 Writing Unit Tests • ✅ Assertions $this->assertInstanceOf(Response::class, $response); $this->assertEquals(200, $response->getStatusCode()); $this->assertSame(401, $responseActual->getStatusCode()); $this->assertTrue($dispatched); $this->assertFalse($sent);
  35. 35. 🧑💻 Writing Unit Tests • ✅ Assertions $this->expectException(RuntimeException::class); $this->expectExceptionCode(403); $this->expectExceptionMessage(‘Configuration not found.');
  36. 36. 🧑💻 Writing Unit Tests • Mocking - Built-in Mock Objects - Prophecy - Mockery
  37. 37. 🧑💻 Writing Unit Tests • ⚠ Scratching the surface - Dive deep into each - See what you like, what works - Leverage a mix
  38. 38. 🧑💻 Writing Unit Tests • Built-in Mock Objects - Only mock certain methods, let others run as-is - Pattern: ->method(…)->with($arg)->willReturn($value) use PHPUnitFrameworkTestCase; class RectangleTest extends TestCase
  39. 39. 🧑💻 Writing Unit Tests • Built-in Mock Objects $adderMock = $this->getMockBuilder(Adder::class) ->onlyMethods(['add']) ->getMock(); $adderMock->method('add') ->with($length, $width) ->willReturn(10.34001); $multiplierMock = $this->getMockBuilder(Multiplier::class) ->onlyMethods(['multiply']) ->getMock(); $multiplierMock->method('multiply') ->with(2, 10.34001) ->willReturn(20.68002); $rectangle = new Rectangle( $length, $width, $adderMock, $multiplierMock );
  40. 40. 🧑💻 Writing Unit Tests • Prophecy - Pattern: ->shouldBeCalled()->willReturn($value)
  41. 41. 🧑💻 Writing Unit Tests • Prophecy - Mock objects - Expected method calls - Reveal object when injecting use PHPUnitFrameworkTestCase; use ProphecyPhpUnitProphecyTrait; class RectangleTest extends TestCase { use ProphecyTrait;
  42. 42. 🧑💻 Writing Unit Tests • Prophecy $adderMock = $this->prophesize(Adder::class); $multiplierMock = $this->prophesize(Multiplier::class); $adderMock->add($length, $width) ->shouldBeCalled() ->willReturn(10.34001); $multiplierMock->multiply(2, 10.34001) ->shouldBeCalled() ->willReturn(20.68002); $rectangle = new Rectangle( $length, $width, $adderMock->reveal(), $multiplierMock->reveal() );
  43. 43. 🧑💻 Writing Unit Tests • Prophecy $dbMock->fetchRow(Argument::any()) ->shouldBeCalled() ->willReturn([]); $asyncBusMock ->dispatch( Argument::type(DoSomethingCmd::class), Argument::type('array') ) ->shouldBeCalled() ->willReturn((new Envelope(new stdClass())));
  44. 44. 🧑💻 Writing Unit Tests • Mockery - Similar style to built-in
  45. 45. 🧑💻 Writing Unit Tests • Mockery $adderMock = Mockery::mock(Adder::class); $multiplierMock = Mockery::mock(Multiplier::class); $adderMock->shouldReceive('add') ->with($length, $width) ->andReturn(10.34001); $multiplierMock->shouldReceive('multiply') ->with(2, 10.34001) ->andReturn(20.68002); $rectangle = new Rectangle( $length, $width, $adderMock, $multiplierMock );
  46. 46. ⌨ Testing Existing Code
  47. 47. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Long and complex functions
  48. 48. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Missing Dependency Injection (DI) ‣ `new Thing();` in functions to be tested
  49. 49. ⌨ Testing Existing Code • ⚠ Problematic Patterns - exit - die - print_r - var_dump - echo - other outputs in-line ‣ Hint: use expectOutputString
  50. 50. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Time-sensitive - sleep
  51. 51. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Database interactions - Resources
  52. 52. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Out of class code execution - Functional code
  53. 53. ⌨ Testing Existing Code • ✅ Helpful Patterns - Unit testing promotes good code patterns
  54. 54. ⌨ Testing Existing Code • ✅ Helpful Patterns - Dependency Injection - Classes and functions focused on one thing - Abstraction - Interfaces - Clean code
  55. 55. ⌨ Testing Existing Code • ✅ Helpful Patterns - Code that is SOLID ‣ Single-responsibility: every class should have only one responsibility ‣ Open-closed: should be open for extension, but closed for modi fi cation ‣ Liskov substitution: in PHP, use interface/abstract ‣ Interface segregation: Many client-speci fi c interfaces are better than one general-purpose interface ‣ Dependency inversion: Depend upon abstractions, [not] concretions
  56. 56. ⌨ Testing Existing Code • ✅ Helpful Patterns - DDD
  57. 57. ⌨ Testing Existing Code • ✅ Helpful Patterns - KISS - DRY - YAGNI
  58. 58. ⌨ Testing Existing Code class ShapeService { public function create(string $shape): int { $db = new Db(); return $db->insert('shape', ['shape' => $shape]); } public function smsArea(Rectangle $shape, string $toNumber): bool { $sms = new Sms([ 'api_uri' => 'https://example.com/sms', 'api_key' => 'alkdjfoasifj0392lkdsjf', ]); $sent = $sms->send($toNumber, 'Area is ' . $shape->area()); (new Logger()) ->log('Sms sent to ' . $toNumber . ': Area is ' . $shape->area()); return $sent; } }
  59. 59. ⌨ Testing Existing Code class ShapeService { public function create(string $shape): int { $db = new Db(); return $db->insert('shape', ['shape' => $shape]); } public function smsArea(Rectangle $shape, string $toNumber): bool { $sms = new Sms([ 'api_uri' => 'https://example.com/sms', 'api_key' => 'alkdjfoasifj0392lkdsjf', ]); $sent = $sms->send($toNumber, 'Area is ' . $shape->area()); (new Logger()) ->log('Sms sent to ' . $toNumber . ': Area is ' . $shape->area()); return $sent; } }
  60. 60. ⌨ Testing Existing Code class ShapeService { public function __construct( protected Db $db, protected Sms $sms ) {} public function create(string $shape): int { return $this->db->insert('shape', ['shape' => $shape]); } public function smsArea(ShapeInterface $shape, string $toNumber): bool { $area = $shape->area(); return $this->sms->send($toNumber, 'Area is ' . $area); }
  61. 61. ⌨ Testing Existing Code use ProphecyTrait; protected Generator $faker; public function setUp(): void { $this->faker = Factory::create(); } public function testCreate() { $dbMock = $this->prophesize(Db::class); $smsMock = $this->prophesize(Sms::class); $shape = $this->faker->word; $dbMock->insert('shape', ['shape' => $shape]) ->shouldBeCalled() ->willReturn(1); $shapeServiceCleanedUp = new ShapeServiceCleanedUp( $dbMock->reveal(), $smsMock->reveal() ); $shapeServiceCleanedUp->create($shape); }
  62. 62. ⌨ Testing Existing Code public function testSmsArea() { $dbMock = $this->prophesize(Db::class); $smsMock = $this->prophesize(Sms::class); $shapeMock = $this->prophesize(ShapeInterface::class); $area = $this->faker->randomFloat(); $shapeMock->area() ->shouldBeCalled() ->willReturn($area); $toNumber = $this->faker->phoneNumber; $smsMock->send($toNumber, 'Area is ' . $area) ->shouldBeCalled() ->willReturn(1); $shapeServiceCleanedUp = new ShapeServiceCleanedUp( $dbMock->reveal(), $smsMock->reveal() ); $shapeServiceCleanedUp->smsArea( $shapeMock->reveal(), $toNumber ); }
  63. 63. 💬 Discussion Items • Convincing Teammates • Convincing Management
  64. 64. 💬 Discussion Items • Does unit testing slow development down?
  65. 65. 💬 Discussion Items • “I don’t see the bene fi t of unit testing”
  66. 66. 💬 Discussion Items • Unit tests for legacy code
  67. 67. 💬 Discussion Items • Other?
  68. 68. 📝 Review • Bene fi ts of Unit Testing • PHPUnit Setup • Writing Unit Tests • Testing Existing Code
  69. 69. Unit Testing from Setup to Deployment • 🙋 Questions? • https://joind.in/talk/906c6
  70. 70. Mark Niebergall @mbniebergall • PHP since 2005 • Masters degree in MIS • Senior Software Engineer • Vulnerability Management project (security scans) • Utah PHP Co-Organizer • CSSLP, SSCP Certi fi ed and Exam Developer • Endurance sports, outdoors
  71. 71. ✏ References • https://analyze.co.za/wp-content/uploads/ 2018/12/441-1170x500.jpg • http://www.ambysoft.com/artwork/ comparingTechniques.jpg • https://en.wikipedia.org/wiki/SOLID

×