In short: PHPUnit is the standard tool for automatically testing PHP code; Pest is a framework built on top of it with a shorter notation. The current version is PHPUnit 13 (since February 2026, from PHP 8.4), and Pest is at version 5. Automated tests do not close security flaws in PHP itself, but they make updates plannable: after PHP2Shell and before PHP 8.2 reaches end of support on 31 December 2026, that is the decisive advantage.
Why test PHP code?
For management, the benefit can be summed up in one sentence: tests make changes cheaper and safer. An automated test checks in seconds whether a function still does what it should, for example whether a shopping cart calculates correctly or a login rejects wrong passwords. Without tests, a person checks this by hand, or nobody does.
The difference is especially clear with updates. When PHP has to be updated at short notice because of a critical vulnerability, many operators hesitate because they fear errors in the shop or the customer portal. With a test suite, the update first runs in a test environment, the tests reveal problems, and only then does it go live.
PHPUnit: the standard for PHP tests
PHPUnit appears in a new major version every February. Each version requires a minimum PHP version and receives bug fixes for about two years. According to phpunit.de:
| PHPUnit | Released | PHP requirement | Bug fixes until |
|---|---|---|---|
| 13 | 6 Feb 2026 | from PHP 8.4 | 4 Feb 2028 |
| 12 | 7 Feb 2025 | from PHP 8.3 | 5 Feb 2027 |
| 11 | 2 Feb 2024 | from PHP 8.2 | 6 Feb 2026 |
| 10 | 3 Feb 2023 | from PHP 8.1 | 7 Feb 2025 |
The table shows a practical connection: a project that still uses PHPUnit 10 usually also still runs on an old PHP version. Test tools and PHP version should be raised together. PHPUnit is usually installed via Composer as a development dependency.
A first test with PHPUnit
A test is a class that extends PHPUnit\Framework\TestCase. Test methods are public and begin with test; alternatively, they are marked with the attribute #[Test]. The example tests a simple price calculation function:
<?php declare(strict_types=1);
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
final class BruttopreisTest extends TestCase
{
public static function preise(): array
{
return [
'Normalsatz' => [10000, 19, 11900],
'Ermäßigt' => [10000, 7, 10700],
'Null bleibt Null' => [0, 19, 0],
];
}
#[DataProvider('preise')]
public function testBruttopreisInCent(int $netto, int $satz, int $erwartet): void
{
$this->assertSame($erwartet, bruttopreis($netto, $satz));
}
}
The data provider supplies several cases for the same test. According to the PHPUnit documentation, it must be public and static. Calculations are done in cents as integers so that no rounding errors arise from floating-point numbers. The suite is run with ./vendor/bin/phpunit if PHPUnit is installed via Composer.
Pest: the same foundation, shorter notation
According to its documentation, Pest is built on PHPUnit. Tests are written as functions with it() or test(), and expectations with expect(). Pest 5 requires PHP 8.4. The installation according to the documentation:
composer remove phpunit/phpunit
composer require pestphp/pest --dev --with-all-dependencies
./vendor/bin/pest --init
The same test in Pest notation:
<?php
it('berechnet den Bruttopreis zum Normalsatz', function () {
expect(bruttopreis(10000, 19))->toBe(11900);
});
The plugin pestphp/pest-plugin-drift converts existing PHPUnit tests mostly automatically. Which notation a team chooses is a matter of taste; what matters is that tests run regularly.
Which test types make sense
- Unit tests: check individual functions and classes without a database or network.
- Integration tests: check the interplay with the database, file system or interfaces.
- Functional tests over HTTP: call pages and forms the way a browser does.
- Security regression tests: record fixed errors, for example that output is escaped with
htmlspecialchars()or that a login fails after a wrong password. - Smoke tests after deployment: a few quick checks that the live site responds after an update.
In addition to tests, static analysis with PHPStan and Psalm finds errors without executing the code.
Tests as a safety net for updates and PHP2Shell
PHP2Shell has shown how short the time between publication and pressure to act can be: announcement on 20 October 2026, patches on 21 October 2026, BSI (German Federal Office for Information Security) warning level orange. Anyone who had a test suite could install PHP 8.4.27 or 8.3.36 in a test environment, run the tests and go live the same day. Without tests, the choice was often only between an unchecked update and an open vulnerability.
The benefit is even greater when changing the major version. There are no more official updates for PHP 8.1 and older, and PHP 8.2 ends on 31 December 2026, as the page PHP end of life explains. A test run against the target version shows early which deprecated functions must be replaced. A CI pipeline that runs tests against several PHP versions makes sense, along with a look at release candidates such as PHP 8.6.0, though never in production.
Write a test for every security bug fixed in your application. That way you make sure the same bug does not return with a later change.
How does PHP SOS help?
For the migration from PHP 8.1 and older and for ongoing updates, we help as follows:
- Assess the inventory: Which tests exist, which PHP and PHPUnit versions are running, where are tests missing for critical processes?
- Create minimum coverage: Together with your development team or agency, we secure login, forms and order paths with tests.
- Test updates: New PHP versions first run in a test environment against the suite.
- Document the result: You receive test results and version states as evidence for the patch or migration step.
Frequently asked questions
Which PHPUnit version do I need?
That depends on your PHP version. PHPUnit 13 requires PHP 8.4, PHPUnit 12 PHP 8.3 and PHPUnit 11 PHP 8.2. The current table is on phpunit.de under supported versions.
Is Pest a replacement for PHPUnit?
According to its own documentation, Pest is built on PHPUnit and offers a shorter notation. Existing PHPUnit tests can be mostly converted automatically with the Drift plugin; some have to be adjusted by hand.
Do tests help against vulnerabilities in PHP itself?
Not directly. Flaws like PHP2Shell sit in the interpreter and are closed by a PHP update. Tests do ensure, however, that you can apply this update quickly, without fear of unnoticed errors in the application.
How many tests are enough?
There is no fixed number. It makes sense to cover the business-critical paths first: login, ordering, forms, interfaces. After that, test coverage grows with every change and every fixed bug.