Notes on software development

How to create a PHP package. Part 2: PHPUnit

This is the second part of a series on creating a PHP package.

In the previous part, we initialized a project using the composer and wrote the code itself. In this part, we will integrate the PHPUnit into our project and write tests.

Code should be covered with tests. This will help to avoid bugs when modifying the code. When we connect the CI to the repository, tests will run automatically as pull requests are created.

1. Installing PHPUnit

Let's add PHPUnit to our project as a dev dependency. To do this, run in the console:

composer require --dev phpunit/phpunit

Note that we specified --dev option. This means that we want to include itin as a dev dependency.

Dev dependencies only needed during development and not needed in production.

In production, we will install dependencies with --no-dev option that prevents dev dependencies from being installed.

A new dependency will appear in the composer.json file:

"require-dev": {
    "phpunit/phpunit": "^10.2"

After executing the command, the composer will also create another composer.lock file.

What are the differences between composer.json and composer.lock files?

When dependencies are installed, the current versions of libraries are written to the composer.lock file.

When we runcomposer install, we will download the versions of the packages that are specified by composer.lock.

But composer update will re-read composer.json, install the latest versions of libraries and update the composer.lock file to keep the latest versions.

Usually, composer update is executed during development and composer install is executed during deployment.

This allows keeping the same versions of dependencies across all servers and guarantees that the code deployed across the cluster will work the same.

Our tests will be located in the tests folder. We need to tell the composer to load these files, but only in the development environment. To do this after require-dev add another section:

"autoload-dev": {
    "psr-4": {
        "Demyanovs\\PHPHighlight\\Tests\\": "tests/"

2. Writing unit tests

Now let's write the tests themselves.


We can run each test from the console:

./vendor/bin/phpunit tests/HighlighterTest.php

To run all tests at once, we need to create a configuration file.

3. Configuring PHPUnit with XML

To simplify the testing process and run all tests at once, let's create the phpunit.xml.dist configuration file at the root of the project.

What are the differences between phpunit.xml.dist and phpunit.xml?

Usually phpunit.xml.dist contains configurations, default values, and secrets that can't be shared or committed to the repository.
In production, we can override these values with the phpunit.xml file.

phpunit.xml. should be ignored by git and should not be committed to your repository. It may contain confidential information, such as database access.

It is similar to how Symfony overwrites the environment variable values ​​from the .env file using the .env.local file.

I will give a ready-made version of the file, but it can contain many settings.

See the documentation for details.

<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi=""
      <text outputFile="php://stdout" showOnlySummary="true"/>
    <ini name="memory_limit" value="-1"/>
    <ini name="display_errors" value="1"/>
    <ini name="error_reporting" value="-1"/>
    <testsuite name="general">
      <directory suffix=".php">./src</directory>

Now to run all the tests at once just execute the command


Moreover, let's add this command to the composer into scripts section:

"scripts": {
    "test": "./vendor/bin/phpunit"

Now we can run this script with the command:

composer test

It's convenient to group different commands in the composer. We will add more commands later.

4. Code coverage

PHPUnit can also check how many tests are covered in your code. Programs with high test coverage have a lower chance of containing bugs than programs with low coverage.

The necessary settings are already in the XML file, but you also need to install Xdebug for PHP and enable xdebug.mode = coverage directive in php.ini.

Now brief information about code coverage is displayed in the console.

PHP package. Part 2: PHPUnit

The report can also be generated in HTML form with a detailed description of classes and methods. To generate such report run the command

./vendor/bin/phpunit --coverage-html=report-html

where report-html is the name of the folder where details will be saved.

What's next?

This time we covered our code with tests. Next, we will integrate automatic code style validation.