php[architect] logo

Want to check out an issue? Sign up to receive a special offer.

Automated Testing Using PHPUnit

Posted by on March 20, 2023

As developers, we spend a lot of time testing the code that we’ve written. Generally, this is a manual process where we write a little code and then manually enter some inputs to verify we get what we expect.

What if instead of doing the same manual testing over and over again, we used a tool that would allow us to write tests that check an input with an expected output. Then we can quickly know if something has been broken. That’s where an automated testing tool like PHPUnit comes into the picture.

In this article, we’ll discuss what PHPUnit is, how to install it and show you how to get started using it today.

What is PHPUnit?

PHPUnit is a programmer-oriented testing framework for PHP. PHPUnit is written in PHP, and it allows us to use PHP code to test our application. It is an instance of the xUnit architecture for unit testing frameworks, so concepts that exist in PHPUnit may seem similar in other xUnit-based frameworks.

PHPUnit can be used to do all kinds of testing, but the two main types we’ll discuss are unit testing and integration testing.

Unit testing is a testing method where individual units of source code are tested to determine whether they are fit for use. A unit test generally tests a single class so we can isolate side effects in our tests. That way a change in one class doesn’t affect multiple tests.

Ideally, a unit test is small and can be run quickly. There are exceptions to this rule, but for the most part, the focus is on speed and how a single class acts independently.

We can also use PHPUnit to write integration tests. Integration tests are used to test how multiple classes interact with each other. These tend to be slower to set up and slow to run. That being said, they can be extremely powerful to test to make sure our application is accessible through things like checking that a route can be accessed by a user.

I try to write unit tests where I can, but in order to verify the health of my application, I use a lot of integration tests.

Ideally, our tests won’t interact with anything external to our application, such as external APIs, services, and databases. Sometimes we can’t get around this limitation. For example, we might have a reporting module where its primary function is to get data from a database, and we need to verify that we can do this.

There are many other testing frameworks for testing in PHP, but PHPUnit is the de-facto testing framework for PHP. Make sure you’re subscribed to this channel, as we’ll be discussing more of these frameworks in the future.

Installing PHPUnit

There are several ways to install PHPUnit. Several of them appear to be around from the days before Composer existed but unless there’s a very specific reason you need the PHAR version, we recommend PHPUnit be installed using Composer.

composer require --dev phpunit/phpunit ^10

Running PHPUnit

PHPUnit is a command-line tool, and as a command-line tool, there are a lot of parameters that make it super powerful. This power allows us to run just some of our tests depending on how we call PHPUnit.

By default, we can run PHPUnit without any parameters, and it will run all of our tests. This is how we run it before we deploy our changes to production.

./vendor/bin/phpunit

As our test suite grows, our tests are going to take a longer and longer time to run. To speed up our development, we’re not going to want to run all the tests every time we make a small change, so it’s important to target tests for our specific change.

We can pass the path of a specific test file we want to run, and it will run just the tests inside that file. This is helpful if we want to make sure our changes to a file didn’t affect any other functionality in that class.

./vendor/bin/phpunit tests/Name/FirstNameTest.php

We can use the --filter parameter to run tests that contain specific words. This is helpful if we want to isolate our run of PHPUnit to just tests that affect a specific type of test. For example, we might have changed our `User` class and we want to run any tests that have “User” in the name of the test class or test method.

./vendor/bin/phpunit —filter User

Finally, we can filter it so we run a specific test in a specific file. This is a good way to run just the test we’re working on to speed us up and not get distracted by other tests failing.

phpunit —filter test initialization tests/Name/FirstNameTest.php

As a quick tip, when I’m developing a new test, I start by running just the new test using the filter/file option above. Once that’s working, I’ll run the whole file to make sure I didn’t break a related test. Finally, I’ll run all of the tests to make sure I didn’t affect another class that has a dependency on the class I changed.

While PHPUnit is a command-line tool by default, there are some huge efficiency gains from looking into how our IDE can call PHPUnit directly. When we’re manually running our tests, we have to make some changes in our code, switch over to our terminal, type in the command we need, and then **finally** we can see the results of our change. With IDE integration, there is generally a button we can use to run a single test or a whole file, or better yet, there should be a keyboard shortcut.

Organization

We love how clean the organization of PHPUnit tests is! All test files are inside a tests folder separate from our application logic. Some testing frameworks have the test file in the same directory as the code under test, which is great for finding the test, but the separation makes it easier to search if we have to perform a global search and replace. The downside to this is that it’s not always clear how to structure our tests for maintainability.

PHPUnit uses PHP classes to group similar tests together. We almost always want a test class for each application class with logic. We generally don’t write tests for models that have no logic and are only there so we can use our framework’s ORM or that are being used to house constants.

My suggestion is that you should be able to easily go from the file you’re working on to its associated tests. I do this by simply replacing “src” with “tests” and can then quickly jump to the test file. This isn’t the only way, but it’s what tends to happen in most open-source libraries.

phpunit.xml

The only other thing we’re going to need to get started using PHPUnit is a `phpunit.xml` file in the root of our project. This file will be loaded automatically by PHPUnit when it loads if it’s in the directory we’re calling the PHPUnit executable from.

To generate this initially, we’ll use the ./vendor/bin/phpunit --generate-configuration which will then prompt us for some information.

./vendor/bin/phpunit --generate-configuration

My `phpunit.xml` is listed on the screen and can be found in the companion article on the PHP Architect website.

<?xml version=”1.0″ encoding=”UTF-8″?>
<phpunit xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
               xsi:noNamespaceSchemaLocation=”https://schema.phpunit.de/10.0/phpunit.xsd”
               bootstrap=”vendor/autoload.php”
               cacheDirectory=”.phpunit.cache”
               executionOrder=”depends,defects”
               requireCoverageMetadata=”true”
               beStrictAboutCoverageMetadata=”true”
               beStrictAboutOutputDuringTests=”true”
               failOnRisky=”true”
               failOnWarning=”true”>
<testsuites>
     <testsuite name=”default”>
           <directory>tests</directory>
     </testsuite>
</testsuites>
<coverage>
       <include>
             <directory suffix=”.php”>src</directory>
        </include>
</coverage>
</phpunit>

You should include this as part of your commits as it will be used on other computers running the same tests.

Writing Our First Test

Again, PHPUnit organizes tests inside PHP classes. We can name our test classes any way we want as long as it’s a valid PHP class name. Each class also needs to end with “Test” or PHPUnit won’t autoload that file by default. This can be changed in our phpunit.xml file, but it’s best to stick to the standard. The class also needs to extend \PHPUnit\Framework\TestCase.

<?php
namespace Tests\Name;
use PHPUnit\Framework\TestCase;
class FirstNameTest extends TestCase
{
}

Now that we’ve created our test class, we can start adding some tests. In PHPUnit, individual tests are just public class methods. I highly recommend making the name of the function something that expresses the intent of what you’re testing. I try to make it read like a sentence so someone can come along and read the name and know exactly the goal of the test.

There are two ways to create functions that PHPUnit will use as a test. The first is to name our test, starting with the word “test.”<?php
namespace Tests\Name;

<?php
namespace Tests\Name;
use ScottsValueObjects\Name\FirstName;
use PHPUnit\Framework\TestCase;
class FirstNameTest extends TestCase
{
public function testCanGetStringLength(): void
{
$firstName = new FirstName(“Scott”);
$this->assertEquals(5, $firstName->length());
}
}

The second is to use the `@test` annotation in the DocBlock for the method.

<?php
namespace Tests\Name;
use ScottsValueObjects\Name\FirstName;
use PHPUnit\Framework\TestCase;
class FirstNameTest extends TestCase
{
/**
* @test
*/
public function canGetStringLength(): void
{
$firstName = new FirstName(“Scott”);
$this->assertEquals(5, $firstName->length());
}
}

Which one you choose is a personal/team preference.

Each test should call at least one assertion method. Assertion methods are used to assert that a value from our code matches an expected value.

If we don’t have an assertion in our code, PHPUnit will mark it as a risky test and, depending on our settings, cause it to fail. I personally see not having an assertion as a definite reason to fail.

1) Tests\Name\FirstNameTest::testCanGetStringLength
This test did not perform any assertions

PHPUnit ships with a [LOT of assertions](https://phpunit.readthedocs.io/en/10.0/assertions.html), and it seems like every release contains more. Assertion methods start with “assert” and then detail what it’s asserting.

$this->assertEquals($expectedValue, $value);
$this->assertFalse($value);
$this->assertGreaterThan($number, $value);

If the value we’re sending the assertion doesn’t match the expected value, the test will be marked as “failed” when we run PHPUnit and we’ll get a nice listing of all the failed tests in the output.

There was 1 failure:

1) Tests\Name\FirstNameTest::testCanGetStringLength
Failed asserting that 5 matches expected 6.

/Users/scottkeck-warren/Projects/ScottsValueObjects/tests/Name/FirstNameTest.php:12

Creating Your Own TestCase

One of the best pieces of advice we can give about how to best structure test code is to create a custom `TestCase` class for each package you’re developing. This will allow us to create helper functions that perform common setup code and create our assertion methods.

<?php
namespace Tests;
use PHPUnit\Framework\TestCase as BaseTestCase;
abstract class TestCase extends BaseTestCase
{
public function createTestUser(): User
{
$user = new User();
// bunch of initialization
return $user;
}
public function assertUserHasAccess(User $user, Event $event): void
{
$this->assertTrue($user->hasAccess($event));
}
}

What You Need To Know

  • PHPUnit is a unit testing framework for PHP
  • Tests are put into the tests directory of your project
  • Tests are organized by having one test file per model
  • Don’t be afraid to create test files with common initial conditions
  • Each test must contain at least one assertion method call

Tags: ,
 

Leave a comment

Use the form below to leave a comment: