Please wait

Unit Testing

Testing in PHP is a crucial practice to ensure that code behaves as expected. It helps in identifying errors, inconsistencies, or any deviation from the expected functionality.

There are different levels of testing, like unit testing, integration testing, and functional testing. This chapter is going to focus on unit testing.

  • Unit testing focuses on individual components, testing them in isolation. Tools like PHPUnit are commonly used for this purpose.
  • Integration testing ensures that different parts of the application work together as intended.
  • Functional testing verifies that the system as a whole functions correctly according to specified requirements.

Automating these tests helps in quicker validation during development cycles, leading to more robust and reliable code. Regular testing becomes an essential part of the development process, promoting better design and higher quality in PHP applications.

The most popular type of testing is unit testing. This chapter is going to focus solely on unit testing.

Diving Deeper into Unit Testing

A unit is the smallest testable part of an application, like a single function or method. It's a critical part of maintaining code quality, especially in PHP development.

The primary goal of unit testing is to take the smallest piece of testable software, isolate it from the rest of the code, and determine whether it behaves exactly as expected.

Writing a Unit Test

When writing a unit test, you follow these general steps:

  1. Choose the Unit to Test: Identify the function or method to test. It should have a clear, defined purpose.
  2. Set Up the Environment: Prepare any data or state required for the function to execute. This might involve creating objects, configuring parameters, etc.
  3. Create the Test Case: Write the actual test, often using a testing framework like PHPUnit. A test case will call the function with specific inputs and then assert that the output is as expected.
  4. Assert the Outcome: Assert statements are used to check that the function's output is what you expect. If the assertion fails, the test fails, indicating a problem.
  5. Clean Up: Any resources set up for the test should be released to return the system to its original state.

Installing PHPUnit

Testing can be performed manually. However, it's recommended to use a library to automate the process. PHPUnit is a popular testing framework for PHP that enables developers to write and run unit tests. It provides various assertions to check the behavior of the PHP code, making it easier to write tests that ensure code quality and functionality.

By using PHPUnit, you can create automated tests that ensure the smallest units of your code (like functions or methods) behave as expected. There are many benefits to using it:

  • PHPUnit enables automated testing, allowing you to run a suite of tests with a single command.
  • You can reuse test cases across different parts of your application, promoting consistency.
  • PHPUnit provides detailed reports on the test results, showing which tests passed, failed, or were skipped.

Installing PHPUnit is typically done via Composer, a dependency management tool for PHP.

Ensure Composer is Installed

If you don't have Composer, you'll need to install it. You can find instructions on the official Composer website. https://getcomposer.org/doc/00-intro.md

Navigate to your project's root directory. Type the following command to require PHPUnit as a development dependency:

composer require --dev phpunit/phpunit

Verify the Installation: Once installed, you can verify the installation by running:

./vendor/bin/phpunit --version

This command should display the installed version of PHPUnit.

Writing a Class

For our first test, let's write a class. The class and test for the class are going to exist in a single file. Typically, these would be in separate files, but for the sake of simplicity, everything is going to exist in a single file.

It's recommended to dedicate a folder called tests to the root directory of your project. Inside this folder, create a file called UserTest.php with the following code:

tests/UserTest.php
class User
{
  public function __construct(public string $name)
  {
  }
 
  public function greet(): string
  {
    return "Hi! My name is " . $this->name . ".";
  }
}

In the above example, we have a class called User with a property called $name. There's a method called greet(), which returns a simple message with the name from the class.

Writing a Test

Next, we must import a class called TestCase from the PHPUnit\Framework namespace.

tests/UserTest.php
use PHPUnit\Framework\TestCase;

This class provides useful methods for performing tests. If we want to write a test, we'll need it. After importing this class, we must define a new class extending the TestCase class.

tests/UserTest.php
final class UserTest extends TestCase
{
 
}

Within this class, we're allowed to define methods that represent a single test. The method name can be anything you want. Typically, developers use the word test followed by what it is being tested. For example, let's say we wanted to test the greet() method; the test would be called testGreet().

tests/UserTest.php
public function testGreet()
{
  $user = new User('John');
 
  $this->assertIsString($user->greet());
  $this->assertStringContainsStringIgnoringCase(
    'John', $user->greet()
  );
}

In the example above, a new instance of the User class is created with the name 'John'. Afterward, we're performing two test assertions.

What is a test assertion?

A test assertion is like a checkpoint in code testing, where you verify that something specific is true. It's a statement that compares the expected outcome with the actual result. If the assertion is true, the test continues; if false, the test fails, indicating a problem.

Think of assertions like the questions in a math quiz. The quiz (test) checks your understanding of a topic (code), and each question (assertion) verifies a specific part of that understanding. If you answer a question wrong, it doesn't mean you've failed the entire quiz, but it does highlight an area where you might need more study or understanding. Similarly, a failed assertion points to a specific part of the code that needs attention.

Next, the $this->assertIsString() method tests that the greet() method of the User class returns a string. If greet returns anything other than a string, this assertion will fail, causing the test to fail.

The $this->assertStringContainsStringIgnoringCase(); method tests that the string returned by the greet method contains the substring 'John', ignoring any differences in case. If the string returned by greet does not contain 'John', this assertion will fail.

These methods become available after inheriting the TestCase class. PHPUnit provides you with a plethora of methods for testing various cases. For a complete list of assertions, you can find them here: https://docs.phpunit.de/en/10.3/assertions.html

Running Tests

Your tests can be executed by running the following command:

./vendor/bin/phpunit --verbose tests

The --verbose option instructs PHPUnit to provide a detailed description of the tests executed. Afterward, we must provide the directory where our tests live. PHPUnit handles scanning the directory for all PHP class files.

After running the command, you'll get the following output:

PHPUnit 9.6.10 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.0RC7
.              1 / 1 (100%)

Time: 00:00.005, Memory: 4.00 MB

OK (1 test, 2 assertions)

Based on this report, we can assume that our test was successfully executed. You'll notice that PHPUnity has identified 1 test and 2 assertions. A test is always a method. Inside a method, we can make as many assertions as we want. In our case, we're making two test assertions. Overall, this is how you would write tests.

Here's the entire class for reference:

tests/UserTest.php
 
use PHPUnit\Framework\TestCase;
 
final class UserTest extends TestCase
{
  public function testGreet()
  {
    $user = new User('John');
 
    $this->assertIsString($user->greet());
    $this->assertStringContainsStringIgnoringCase(
      'John', $user->greet()
    );
  }
}
 
class User
{
  public function __construct(public string $name)
  {
  }
 
  public function greet(): string
  {
    return "Hi! My name is " . $this->name . ".";
  }
}

Key Takeaways

  • Testing ensures that code behaves as expected, helps in identifying errors, and verifies that the system functions correctly.
  • Unit testing focuses on testing individual components or functions in isolation. It checks that small parts of code perform as intended.
  • A popular testing framework for PHP is PHPUnit. Used to write and run unit tests. It provides various assertions to verify the code's behavior.
  • Test assertions are checkpoints where the actual result is compared with the expected result. If the assertion is true, the test continues; if false, the test fails.
  • PHPUnit enables automated testing and provides detailed reports on the test results, making it easier to identify and address issues.

Comments

Please read this before commenting