SlideShare a Scribd company logo
Leveling Up With
Unit Testing
Mark Niebergall
https://joind.in/talk/906c6
👏 Thank You!
• LonghornPHP Organizers, Sponsors
- Nucleus Security
Leveling Up With Unit Testing - LonghornPHP 2022
https://analyze.co.za/wp-content/uploads/2018/12/441-1170x500.jpg
✔ 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
👀 Overview
• 😀 Bene
fi
ts of Unit Testing
• ⚙ PHPUnit Setup
• 🧑💻 Writing Unit Tests
• ⌨ Testing Existing Code
😀 Bene
fi
ts of Unit Testing
😀 Bene
fi
ts of Unit Testing
public static function add($a, $b)
{
return $a + $b;
}
😀 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;
}
😀 Bene
fi
ts of Unit Testing
http://www.ambysoft.com/artwork/comparingTechniques.jpg
😀 Bene
fi
ts of Unit Testing
• 💻 Automated way to test code
- Regression Testing
😀 Bene
fi
ts of Unit Testing
• 💻 Automated way to test code
- Continuous Integration (CI)
- Continuous Deployment (CD)
😀 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?
😀 Bene
fi
ts of Unit Testing
• 🪲 Decrease bugs introduced with code
- Decreased time to deployment
- Better use of QA team time
😀 Bene
fi
ts of Unit Testing
• 🪲 Decrease bugs introduced with code
- High con
fi
dence in delivered code
😀 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
⚙ PHPUnit Setup
⚙ PHPUnit Setup
• Install via composer
• Setup `phpunit.xml` for con
fi
guration (if needed)
• Run unit tests
⚙ PHPUnit Setup
• phpunit/phpunit
• phpspec/prophecy-phpunit
• mockery/mockery
• fakerphp/faker
⚙ PHPUnit Setup
composer require --dev phpunit/phpunit
composer require --dev phpspec/prophecy-phpunit
composer require --dev mockery/mockery
composer require --dev fakerphp/faker
⚙ 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>
⚙ PHPUnit Setup
• 💻 Running PHPUnit
vendor/bin/phpunit tests/
⚙ 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)
⚙ PHPUnit Setup
• 💻 Running PHPUnit
- Within PhpStorm
- https://www.jetbrains.com/help/phpstorm/using-
phpunit-framework.html
⚙ 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
🧑💻 Writing Unit Tests
🧑💻 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;
}
🧑💻 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);
}
🧑💻 Writing Unit Tests
public function testAdderThrowsExceptionWhenNotANumber()
{
$this->expectException(TypeError::class);
$adder = new Adder();
$adder->add(7, 'Can't add this');
}
🧑💻 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);
}
🧑💻 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],
];
}
🧑💻 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];
}
🧑💻 Writing Unit Tests
• 📏 Test Coverage
- Percent of code covered by tests
- Not aiming for 100%
- No need to test language constructs
🧑💻 Writing Unit Tests
• ⛔ Self-contained
- No actual database connections
- No API calls should occur
- No external code should be called
‣ Use testing framework
🧑💻 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);
🧑💻 Writing Unit Tests
• ✅ Assertions
$this->expectException(RuntimeException::class);
$this->expectExceptionCode(403);
$this->expectExceptionMessage(‘Configuration not found.');
🧑💻 Writing Unit Tests
• Mocking
- Built-in Mock Objects
- Prophecy
- Mockery
🧑💻 Writing Unit Tests
• ⚠ Scratching the surface
- Dive deep into each
- See what you like, what works
- Leverage a mix
🧑💻 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
🧑💻 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
);
🧑💻 Writing Unit Tests
• Prophecy
- Pattern: ->shouldBeCalled()->willReturn($value)
🧑💻 Writing Unit Tests
• Prophecy
- Mock objects
- Expected method calls
- Reveal object when injecting
use PHPUnitFrameworkTestCase;
use ProphecyPhpUnitProphecyTrait;
class RectangleTest extends TestCase
{
use ProphecyTrait;
🧑💻 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()
);
🧑💻 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())));
🧑💻 Writing Unit Tests
• Mockery
- Similar style to built-in
🧑💻 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
);
⌨ Testing Existing Code
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- Long and complex functions
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- Missing Dependency Injection (DI)
‣ `new Thing();` in functions to be tested
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- exit
- die
- print_r
- var_dump
- echo
- other outputs in-line
‣ Hint: use expectOutputString
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- Time-sensitive
- sleep
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- Database interactions
- Resources
⌨ Testing Existing Code
• ⚠ Problematic Patterns
- Out of class code execution
- Functional code
⌨ Testing Existing Code
• ✅ Helpful Patterns
- Unit testing promotes good code patterns
⌨ Testing Existing Code
• ✅ Helpful Patterns
- Dependency Injection
- Classes and functions focused on one thing
- Abstraction
- Interfaces
- Clean code
⌨ 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
⌨ Testing Existing Code
• ✅ Helpful Patterns
- DDD
⌨ Testing Existing Code
• ✅ Helpful Patterns
- KISS
- DRY
- YAGNI
⌨ 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;
}
}
⌨ 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;
}
}
⌨ 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);
}
⌨ 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);
}
⌨ 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
);
}
💬 Discussion Items
• Convincing Teammates
• Convincing Management
💬 Discussion Items
• Does unit testing slow development down?
💬 Discussion Items
• “I don’t see the bene
fi
t of unit testing”
💬 Discussion Items
• Unit tests for legacy code
💬 Discussion Items
• Other?
📝 Review
• Bene
fi
ts of Unit Testing
• PHPUnit Setup
• Writing Unit Tests
• Testing Existing Code
Unit Testing from Setup to Deployment
• 🙋 Questions?
• https://joind.in/talk/906c6
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
✏ 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

More Related Content

PDF
Leveling Up With Unit Testing - php[tek] 2023
PDF
Unit Testing from Setup to Deployment
KEY
Developer testing 101: Become a Testing Fanatic
PDF
Fighting Fear-Driven-Development With PHPUnit
PPTX
PHPUnit: from zero to hero
PPTX
Unit Testng with PHP Unit - A Step by Step Training
PPTX
Test in action week 2
Leveling Up With Unit Testing - php[tek] 2023
Unit Testing from Setup to Deployment
Developer testing 101: Become a Testing Fanatic
Fighting Fear-Driven-Development With PHPUnit
PHPUnit: from zero to hero
Unit Testng with PHP Unit - A Step by Step Training
Test in action week 2

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

PPT
Unit testing
ZIP
Test
PDF
Introduction to Unit Testing with PHPUnit
KEY
PHPUnit testing to Zend_Test
PPTX
Test in action – week 1
PPT
Unit testing php-unit - phing - selenium_v2
PPT
Test Driven Development with PHPUnit
KEY
Php Unit With Zend Framework Zendcon09
PPT
Unit Testing using PHPUnit
PPTX
Unit testing
PDF
PHPunit and you
PDF
Unit testing with PHPUnit - there's life outside of TDD
KEY
Developer testing 201: When to Mock and When to Integrate
PPTX
Test in action week 4
PPTX
Getting started-php unit
PPT
Automated Unit Testing
PDF
Getting started with TDD - Confoo 2014
PDF
Php unit (eng)
PDF
Create, test, secure, repeat
PDF
PhpUnit Best Practices
Unit testing
Test
Introduction to Unit Testing with PHPUnit
PHPUnit testing to Zend_Test
Test in action – week 1
Unit testing php-unit - phing - selenium_v2
Test Driven Development with PHPUnit
Php Unit With Zend Framework Zendcon09
Unit Testing using PHPUnit
Unit testing
PHPunit and you
Unit testing with PHPUnit - there's life outside of TDD
Developer testing 201: When to Mock and When to Integrate
Test in action week 4
Getting started-php unit
Automated Unit Testing
Getting started with TDD - Confoo 2014
Php unit (eng)
Create, test, secure, repeat
PhpUnit Best Practices
Ad

More from Mark Niebergall (20)

PDF
Filesystem Management with Flysystem - php[tek] 2023
PDF
Filesystem Management with Flysystem at PHP UK 2023
PDF
Developing SOLID Code
PDF
Stacking Up Middleware
PDF
BDD API Tests with Gherkin and Behat
PDF
BDD API Tests with Gherkin and Behat
PDF
Hacking with PHP
PDF
Relational Database Design Bootcamp
PDF
Starting Out With PHP
PDF
Automatic PHP 7 Compatibility Checking Using php7cc (and PHPCompatibility)
PDF
Debugging PHP with Xdebug - PHPUK 2018
PDF
Advanced PHP Simplified - Sunshine PHP 2018
PDF
Defensive Coding Crash Course Tutorial
PDF
Inheritance: Vertical or Horizontal
PDF
Cybersecurity State of the Union
PDF
Cryptography With PHP - ZendCon 2017 Workshop
PDF
Defensive Coding Crash Course - ZendCon 2017
PDF
Leveraging Composer in Existing Projects
PDF
Defensive Coding Crash Course
PDF
Impostor Syndrome: Be Proud of Your Achievements!
Filesystem Management with Flysystem - php[tek] 2023
Filesystem Management with Flysystem at PHP UK 2023
Developing SOLID Code
Stacking Up Middleware
BDD API Tests with Gherkin and Behat
BDD API Tests with Gherkin and Behat
Hacking with PHP
Relational Database Design Bootcamp
Starting Out With PHP
Automatic PHP 7 Compatibility Checking Using php7cc (and PHPCompatibility)
Debugging PHP with Xdebug - PHPUK 2018
Advanced PHP Simplified - Sunshine PHP 2018
Defensive Coding Crash Course Tutorial
Inheritance: Vertical or Horizontal
Cybersecurity State of the Union
Cryptography With PHP - ZendCon 2017 Workshop
Defensive Coding Crash Course - ZendCon 2017
Leveraging Composer in Existing Projects
Defensive Coding Crash Course
Impostor Syndrome: Be Proud of Your Achievements!
Ad

Recently uploaded (20)

PDF
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
PDF
Softaken Excel to vCard Converter Software.pdf
PDF
Design an Analysis of Algorithms I-SECS-1021-03
PPTX
L1 - Introduction to python Backend.pptx
PPTX
ai tools demonstartion for schools and inter college
PPTX
Reimagine Home Health with the Power of Agentic AI​
PDF
Design an Analysis of Algorithms II-SECS-1021-03
PDF
wealthsignaloriginal-com-DS-text-... (1).pdf
PDF
System and Network Administration Chapter 2
PDF
PTS Company Brochure 2025 (1).pdf.......
PDF
System and Network Administraation Chapter 3
PPTX
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
PPTX
CHAPTER 2 - PM Management and IT Context
PPTX
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
PPTX
Odoo POS Development Services by CandidRoot Solutions
PDF
2025 Textile ERP Trends: SAP, Odoo & Oracle
PDF
top salesforce developer skills in 2025.pdf
PDF
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
PPTX
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
PDF
How to Migrate SBCGlobal Email to Yahoo Easily
Internet Downloader Manager (IDM) Crack 6.42 Build 42 Updates Latest 2025
Softaken Excel to vCard Converter Software.pdf
Design an Analysis of Algorithms I-SECS-1021-03
L1 - Introduction to python Backend.pptx
ai tools demonstartion for schools and inter college
Reimagine Home Health with the Power of Agentic AI​
Design an Analysis of Algorithms II-SECS-1021-03
wealthsignaloriginal-com-DS-text-... (1).pdf
System and Network Administration Chapter 2
PTS Company Brochure 2025 (1).pdf.......
System and Network Administraation Chapter 3
Lecture 3: Operating Systems Introduction to Computer Hardware Systems
CHAPTER 2 - PM Management and IT Context
Agentic AI : A Practical Guide. Undersating, Implementing and Scaling Autono...
Odoo POS Development Services by CandidRoot Solutions
2025 Textile ERP Trends: SAP, Odoo & Oracle
top salesforce developer skills in 2025.pdf
Raksha Bandhan Grocery Pricing Trends in India 2025.pdf
Oracle E-Business Suite: A Comprehensive Guide for Modern Enterprises
How to Migrate SBCGlobal Email to Yahoo Easily

Leveling Up With Unit Testing - LonghornPHP 2022

  • 1. Leveling Up With Unit Testing Mark Niebergall https://joind.in/talk/906c6
  • 2. 👏 Thank You! • LonghornPHP Organizers, Sponsors - Nucleus Security
  • 5. ✔ 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
  • 6. 👀 Overview • 😀 Bene fi ts of Unit Testing • ⚙ PHPUnit Setup • 🧑💻 Writing Unit Tests • ⌨ Testing Existing Code
  • 7. 😀 Bene fi ts of Unit Testing
  • 8. 😀 Bene fi ts of Unit Testing public static function add($a, $b) { return $a + $b; }
  • 9. 😀 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; }
  • 10. 😀 Bene fi ts of Unit Testing http://www.ambysoft.com/artwork/comparingTechniques.jpg
  • 11. 😀 Bene fi ts of Unit Testing • 💻 Automated way to test code - Regression Testing
  • 12. 😀 Bene fi ts of Unit Testing • 💻 Automated way to test code - Continuous Integration (CI) - Continuous Deployment (CD)
  • 13. 😀 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?
  • 14. 😀 Bene fi ts of Unit Testing • 🪲 Decrease bugs introduced with code - Decreased time to deployment - Better use of QA team time
  • 15. 😀 Bene fi ts of Unit Testing • 🪲 Decrease bugs introduced with code - High con fi dence in delivered code
  • 16. 😀 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
  • 18. ⚙ PHPUnit Setup • Install via composer • Setup `phpunit.xml` for con fi guration (if needed) • Run unit tests
  • 19. ⚙ PHPUnit Setup • phpunit/phpunit • phpspec/prophecy-phpunit • mockery/mockery • fakerphp/faker
  • 20. ⚙ PHPUnit Setup composer require --dev phpunit/phpunit composer require --dev phpspec/prophecy-phpunit composer require --dev mockery/mockery composer require --dev fakerphp/faker
  • 21. ⚙ 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>
  • 22. ⚙ PHPUnit Setup • 💻 Running PHPUnit vendor/bin/phpunit tests/
  • 23. ⚙ 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)
  • 24. ⚙ PHPUnit Setup • 💻 Running PHPUnit - Within PhpStorm - https://www.jetbrains.com/help/phpstorm/using- phpunit-framework.html
  • 25. ⚙ 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
  • 27. 🧑💻 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; }
  • 28. 🧑💻 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); }
  • 29. 🧑💻 Writing Unit Tests public function testAdderThrowsExceptionWhenNotANumber() { $this->expectException(TypeError::class); $adder = new Adder(); $adder->add(7, 'Can't add this'); }
  • 30. 🧑💻 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); }
  • 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(): array { return [ [2, 1, 1], [2, 1.5, 0.5], ]; }
  • 32. 🧑💻 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]; }
  • 33. 🧑💻 Writing Unit Tests • 📏 Test Coverage - Percent of code covered by tests - Not aiming for 100% - No need to test language constructs
  • 34. 🧑💻 Writing Unit Tests • ⛔ Self-contained - No actual database connections - No API calls should occur - No external code should be called ‣ Use testing framework
  • 35. 🧑💻 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);
  • 36. 🧑💻 Writing Unit Tests • ✅ Assertions $this->expectException(RuntimeException::class); $this->expectExceptionCode(403); $this->expectExceptionMessage(‘Configuration not found.');
  • 37. 🧑💻 Writing Unit Tests • Mocking - Built-in Mock Objects - Prophecy - Mockery
  • 38. 🧑💻 Writing Unit Tests • ⚠ Scratching the surface - Dive deep into each - See what you like, what works - Leverage a mix
  • 39. 🧑💻 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
  • 40. 🧑💻 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 );
  • 41. 🧑💻 Writing Unit Tests • Prophecy - Pattern: ->shouldBeCalled()->willReturn($value)
  • 42. 🧑💻 Writing Unit Tests • Prophecy - Mock objects - Expected method calls - Reveal object when injecting use PHPUnitFrameworkTestCase; use ProphecyPhpUnitProphecyTrait; class RectangleTest extends TestCase { use ProphecyTrait;
  • 43. 🧑💻 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() );
  • 44. 🧑💻 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())));
  • 45. 🧑💻 Writing Unit Tests • Mockery - Similar style to built-in
  • 46. 🧑💻 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 );
  • 48. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Long and complex functions
  • 49. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Missing Dependency Injection (DI) ‣ `new Thing();` in functions to be tested
  • 50. ⌨ Testing Existing Code • ⚠ Problematic Patterns - exit - die - print_r - var_dump - echo - other outputs in-line ‣ Hint: use expectOutputString
  • 51. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Time-sensitive - sleep
  • 52. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Database interactions - Resources
  • 53. ⌨ Testing Existing Code • ⚠ Problematic Patterns - Out of class code execution - Functional code
  • 54. ⌨ Testing Existing Code • ✅ Helpful Patterns - Unit testing promotes good code patterns
  • 55. ⌨ Testing Existing Code • ✅ Helpful Patterns - Dependency Injection - Classes and functions focused on one thing - Abstraction - Interfaces - Clean code
  • 56. ⌨ 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
  • 57. ⌨ Testing Existing Code • ✅ Helpful Patterns - DDD
  • 58. ⌨ Testing Existing Code • ✅ Helpful Patterns - KISS - DRY - YAGNI
  • 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. ⌨ 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; } }
  • 61. ⌨ 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); }
  • 62. ⌨ 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); }
  • 63. ⌨ 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 ); }
  • 64. 💬 Discussion Items • Convincing Teammates • Convincing Management
  • 65. 💬 Discussion Items • Does unit testing slow development down?
  • 66. 💬 Discussion Items • “I don’t see the bene fi t of unit testing”
  • 67. 💬 Discussion Items • Unit tests for legacy code
  • 69. 📝 Review • Bene fi ts of Unit Testing • PHPUnit Setup • Writing Unit Tests • Testing Existing Code
  • 70. Unit Testing from Setup to Deployment • 🙋 Questions? • https://joind.in/talk/906c6
  • 71. 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
  • 72. ✏ 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