I can't test this code because it's legacy. I need to update the code to make it testable. How can I manage that, without breaking existing functionality? I'll need to write some tests, but... argh!
• Code that wasn’t written by you, just now • Code resulting from incorrect assumptions • Code built using unsupported technology • Code that isn’t tested
• Verification that each individual ‘unit’ of an application works as intended • Units are the component parts of your application • Generally, each behaviour is a unit; likely to be contained within a function • Unit testing is not a substitute for other types of testing
test? • Aid in the documentation of the system • To prevent regressions when changing code • To test integration with new technology • For validation of product requirements
programmer-oriented testing framework for PHP. It is an instance of the xUnit architecture for unit testing frameworks. PHPUnit Reference: https:/ /phpunit.de/
test tests/ExampleTest.php class ExampleTest extends PHPUnit\Framework\TestCase { public function testSomeFunction() { $result = someFunction(); // Should return true $this->assertTrue($result); } }
a simple yet flexible PHP mock object framework for use in unit testing with PHPUnit, PHPSpec or any other testing framework. Reference: https:/ /github.com/mockery/mockery
class ExampleTest extends PHPUnit\Framework\TestCase { public function setUp(): void { $mockObject = Mockery::mock(Object::class); $mockObject->shouldReceive('method') // Expect that ‘method’ ->once() // should be called once ->andReturn(true); // and return true } }
tests/YodelTest.php class PrintedOutputTest extends PHPUnit\Framework\TestCase { public function testNewYodel() { yodel(); // Should echo ‘Odl lay ee’ } public function testEchoedYodel() { yodel('Lay hee hoo'); // Should echo ‘Lay hee hoo’ } }
PHPUnit\Framework\TestCase { public function testUnder9000() { $result = getPowerLevel(); } public function testOver9000() { $result = getPowerLevel(); } } A static method call
tests/PowerLevelTest.php public function testOver9000() { $superSaiyanGoku = Mockery::mock('alias:Goku') ->shouldReceive('powerLevel') // Set Goku’s power level ->andReturn(9001); // to over 9000. . $result = getPowerLevel(); $this->assertEquals('It’s over 9000!', $result); // Failure. }
tests/PowerLevelTest.php public function testOver9000() { // Goku is super saiyan! Mockery::mock('alias:Goku') ->shouldReceive('powerLevel') ->andReturn(9001); . $result = getPowerLevel(); $this->assertEquals('It’s over 9000!', $result); // Success! }
$weather = new WeatherForecast(); // A hard-coded dependency. $outlook = $weather->isSunny() ? "full" : "empty"; return "The glass is half $outlook."; } A hard-coded dependency
PHPUnit\Framework\TestCase { public function testHalfFull() { $result = getOutlook(); } public function testHalfEmpty() { $result = getOutlook(); } } A hard-coded dependency
Mock the WeatherForecast, using a Mockery override. • Set the return value of WeatherForecast::isSunny to be true or false. • Perform assertions based on mocked return values. • Run tests in separate processes.
to use an injected dependency. • Mock the dependency. • Inject the mocked dependency. • Remove @runTestsInSeparateProcesses annotation. Refactoring: hard-coded dependencies
PHPUnit\Framework\TestCase { public function testEnterSafely() { enterCaveOfCaerbannog(); } public function testRunAway() { enterCaveOfCaerbannog($killerRabbit = true); } } Redirections
*/ class EnterCaveOfCaerbannogTest extends PHPUnit\Framework\TestCase { public function testEnterSafely() { … } public function testRunAway() { … } } Redirections
stream wrapper for a virtual file system that may be helpful in unit tests to mock the real file system. It can be used with any unit test framework, like PHPUnit or SimpleTest. Fake filesystem Reference: http:/ /vfs.bovigo.org/
Bolognese { private $eaten; private $parmesan; public function __construct(bool $eaten = false) { … } public function eat() { … } public function isEaten(): bool { … } }
function eat() { if ($this->eaten) return; // Can’t eat twice! if (!$this->parmesan || !$this->parmesan->isEaten()) { $this->parmesan = new Parmesan(); // Must always have parmesan. } (new GarlicBread())->eat(); // Gotta have garlic bread. $this->parmesan->eat(); $this->eaten = true; }
Use @covers annotation to isolate unit under test • Create reusable expectations: ◦ to illustrate different scenarios ◦ to define alternate process branches
test private methods. • Test the public methods that use private methods. • Private methods represent implementation details. • Private methods should remain invisible.