Mastering PHPUnit: an introduction
In this blog series we’ll go from writing our first unit test to being a PHPUnit master. This first post will go over the basics, and introduce you to PHPUnit
What is PHPUnit?
PHPUnit is the de facto testing framework for PHP. It initially started in 2001, over 23 years ago at this point. And even today it is still under active development. All its development is done on GitHub, where you can open issues if you have a feature request, or find a bug.
Why do we test?
There is a lot of talk about whether you should even write unit tests. To summarize my thoughts on the matter: You should. I don’t trust myself (or anyone else) to write perfect code. Which is why we need to validate that our code works. Now we could manually verify every bit of code we write. For web development we could reload a page, click a few buttons, and consider our code working. But, for every change we make we’ll have to do that work again.
With unit testing we validate this automatically. We write a test that checks that our code does what we think it does. Now instead of having to perform all the possible actions of our code every time we refactor, we simply run our unit tests, and we know in a matter of milliseconds if our code works.
The ‘downside’ of unit testing is that we have to write these tests, which means we have to write more code. It also means that on top of code we need to maintain our unit tests as well. Which means dependency updates, refactors etc. In my opinion the investment in unit tests outweighs the costs.
AAA: The three steps to every unit test
In general, we can split up our tests in three parts: arrange, act, assert.
Arrange
In the arrange phase we do all the set up to make sure that we can run the code that is needed.
We may need to set up some mocks (more about those in a future post), set up the environment or do other things.
Act
In the act phase we run the actual code that we want to execute. In unit testing this is usually just one line of code, where we run the code.
Assert
After we run the code that we want to test, we of course need to check that the code did what we
want to do. So we assert
that it did what we expected it to do. Here we’ll check either the output
of the method test, or check for the side effects that the method caused.
Adding a test to a project
Let’s assume we have a project with the following structure. For this i’ll assume you’ve worked with composer before and have autoloading etc. set up.
๐ฆ project
โฃ ๐ src
โ โ ๐ Calculator.php
โฃ ๐ composer.json
With the Calculator.php
looking like this:
<?php
declare(strict_types=1);
namespace App;
class Calculator
{
public function add(int $one, int $two): int
{
return $one + $two;
}
}
First off we’ll run composer require --dev phpunit/phpunit
, to install phpunit as a dev dependency.
Then we’ll create a file called phpunit.xml
to configure phpunit with. (We’ll go into detail about this config file in a future post.)
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="./vendor/phpunit/phpunit/phpunit.xsd"
>
<testsuites>
<testsuite name="AllTests">
<directory>test/</directory>
</testsuite>
</testsuites>
</phpunit>
Then we’ll continue by creating a file called CalculatorTest.php
in the test
folder we’ll create.
So now our project will look like this:
๐ฆ project
โฃ ๐ src
โ โ ๐ Calculator.php
โฃ ๐ test
โ โ ๐ CalculatorTest.php
โฃ ๐ composer.json
โฃ ๐ phpunit.xml
Now we’ll write our first test. For now just an empty test, which doesn’t do anything.
<?php
declare(strict_types=1);
namespace App\Test;
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
public function testAdd(): void
{
}
}
If we now run phpunit, we’ll get an output like this:
$ vendor/bin/phpunit
PHPUnit 11.1.3 by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.6
Configuration: /Volumes/SourceCode/Private/hugo-blog-test/phpu/phpunit.xml
R 1 / 1 (100%)
Time: 00:00.007, Memory: 6.00 MB
There was 1 risky test:
1) App\Test\CalculatorTest::testAdd
This test did not perform any assertions
/Volumes/SourceCode/Private/hugo-blog-test/phpu/test/CalculatorTest.php:12
OK, but there were issues!
Tests: 1, Assertions: 0, Risky: 1.
PHPunit has executed our test, but it didn’t do any assertions, which is a problem.
So let actually test something, and fill in the testAdd
method, like so:
public function testAdd(): void
{
// arrange
$calculator = new Calculator();
//act
$output = $calculator->add(2, 5);
//assert
self::assertSame(7, $output);
}
As you can see we have the three steps, arrange
, act
, assert
. Now in normal development
you wouldn’t explicitly add these comments of the different phases, but it’s good to keep in mind how
you structure your test.
Now if we run phpunit we’ll get the following output:
$ vendor/bin/phpunit
PHPUnit 11.1.3 by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.6
Configuration: /Volumes/SourceCode/Private/hugo-blog-test/phpu/phpunit.xml
. 1 / 1 (100%)
Time: 00:00.003, Memory: 6.00 MB
OK (1 test, 1 assertion)
That’s a success! We have a test, and we made an assertion about our code, now if we update the calculator class we can run the test again, and validate our code still works as before.
Next week we’ll go over how to determine what to test, how to test it, and some common problems we can face while testing. If you want to get notified of the next blog post, join the newsletter.