Testing
After every change you need to make sure nothing broke. Checking a site by hand is slow, and something always gets forgotten. An automated test is code that calls your function and compares the result with what is expected. Hundreds of such checks run in seconds.
The PHPUnit examples need the library installed and cannot be run here.
The idea of a test
A test has three steps: prepare the data, perform the action, compare the result. This can be done without any library:
<?php
function applyDiscount(float $price, int $percent): float
{
return $price * (100 - $percent) / 100;
}
$result = applyDiscount(200, 25);
echo $result === 150.0 ? 'test passed' : 'test failed';A testing library does the same, but it finds the tests itself, runs them and shows a clear report.
Installing PHPUnit
PHPUnit is the standard testing library for PHP. It is installed as a development dependency:
composer require --dev phpunit/phpunitTests go into the tests folder and are run with the command:
vendor/bin/phpunit testsThe first test
A test is a class that extends TestCase. Every method whose name starts with test is a separate check:
<?php
// tests/DiscountTest.php
use PHPUnit\Framework\TestCase;
final class DiscountTest extends TestCase
{
public function testAppliesDiscount(): void
{
$this->assertSame(150.0, applyDiscount(200, 25));
}
public function testZeroDiscountKeepsPrice(): void
{
$this->assertSame(200.0, applyDiscount(200, 0));
}
}assertSame() compares the expected value with the actual one strictly — by both value and type.
The main assertions
| Method | What it checks |
|---|---|
assertSame($expected, $actual) | The values are equal and of the same type |
assertTrue($value) | The value is true |
assertCount(3, $array) | The array has three elements |
assertContains('a', $array) | The array contains the value |
assertNull($value) | The value is null |
Testing exceptions
You also need to check that a function refuses bad data correctly:
<?php
// tests/AccountTest.php
use PHPUnit\Framework\TestCase;
final class AccountTest extends TestCase
{
public function testCannotWithdrawMoreThanBalance(): void
{
$account = new BankAccount();
$account->deposit(100);
$this->expectException(InvalidArgumentException::class);
$account->withdraw(500);
}
}The test passes only if an exception of the given type really occurs after expectException().
The report
If a check fails, PHPUnit shows which test it was and how the expected value differs from the actual one:
There was 1 failure:
1) DiscountTest::testAppliesDiscount
Failed asserting that 160.0 is identical to 150.0.What to test
First of all, code with logic: calculations, validation, data transformation. Such code is easy to test when a function receives everything it needs through parameters and returns a result, rather than reading global variables or printing to the screen.
Common mistake
A test that checks only the "happy path" misses the main bugs. Test the boundaries: zero, an empty string, an empty array, a negative number, a value that is too large. That is where code breaks most often.