Unit Testing With PHPUnit

What is Unit Testing and PHPUnit?

Unit testing is a way of testing a unit, the smallest piece of code, that can be logically isolated in a system. These units are tested against expected results.

In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method. In procedural programming, a unit may be an individual program, function, procedure, etc. Unit testing is done to ensure that the source code written by the developer meets the requirement and behaves in an expected manner.

PHPUnit is a programmer-oriented testing framework for PHP.

Why Unit Testing?

  • Improves design and allows better refactoring of code.
  • Reduces Testing Cost as defects are captured in a very early phase.
  • Unit Tests provide living documentation of an application. Developers wanting to learn what functionality is provided by a particular unit can refer to the Unit Tests to gain understanding.

That's why Sendinblue developers write unit tests and run these tests for every pull request and branch push using Travis CI.

Conventions For Writing Unit Tests

  1. The test file should be named as *Test.php, e.g. for file Product.php test file should be ProductTest.php
  2. Test class (ProductTest) inherits from PHPUnit\Framework\TestCase.
  3. Tests are the methods that should be named as test*, for e.g. testGetName(). Alternatively, you can use the @test annotation in a method’s DocBlock to mark it as a test method.
  4. Test methods are `public`.
  5. Mirror your src/ directory structure in your tests/ folder structure.

NOTE: Above src directory structure is referred from a Symfony project. You may have a completely different code & directory structure.

Installing PHPUnit

composer require --dev phpunit/phpunit

We’re using the --dev flag as we only need this installed for our dev environment, as we don’t want to run these tests in production.

Let’s Get Started

  • In the bootstrap attribute, we mention the script which we want to be loaded before tests are executed.
  • colors attribute is responsible to decide whether the output of PHPUnit will be colorful or not.
  • In testsuites element, we have defined the directory where our tests will be presented.

Let’s now create directory tests where we will write the tests. After this, our project will look like.

Go to the src directory and create a class Calculator:

Go to the tests directory and create your first test class CalculatorTest.php to test Calculator add method:

In the above test, on line number #15 we have used assertEquals to validate if expected and actual values are equal. There are a lot of such assertions available to make our life easier.

Let’s run the test, in terminal go to your project directory and run ./vendor/bin/PHPUnit

Congratulations you have just created and executed your first test.

Data Providers

Rules To Use Data Providers:

  • The data provider method returns either an array of arrays or an object that implements the Iterator interface and yields an array for each iteration step.
  • Data provider method must be public
  • The test method uses annotation(@dataProvider) to declare its data provider method.

Let’s modify our first test with Data Providers

Let’s run the tests again.

OOPS, one test failed for the data set [10.8, 10.1, 20.9]. Here we were expecting that the add method of class Calculator should return 20.9 for passed parameters 10.8 & 10.1. However, it has returned 20 instead of 20.9
It’s evident there is something wrong with the add method.
Aha, found the issue was happening because we are using int for method parameters & return type declarations, let’s modify the add method of the Calculator class to use float instead of int.

Try again running the tests. Hurray, all tests pass.

Code Coverage Analysis

In computer science, code coverage is a measure used to describe the degree to which the source code of a program is tested by a particular test suite. A program with high code coverage has been more thoroughly tested and has a lower chance of containing software bugs than a program with low code coverage.

PHPUnit provides below software metrics for code coverage analysis:

  • Line Coverage: Measures whether each executable line was executed.
  • Function and Method Coverage: Measures whether each function or method has been invoked. It only considers a function or method as covered when all of its executable lines are covered.
  • Class and Trait Coverage: Measures whether each method of a class or trait is covered. It only considers a class or trait is covered when all of its methods are covered.
  • Change Risk Anti-Patterns (CRAP) Index: It is calculated based on the cyclomatic complexity and code coverage of a unit of code. Code that is not too complex and has an adequate test coverage will have a low CRAP index.

In order to check the code coverage of your code, one can simply run the below command in his/her project:

.vendor/bin/phpunit --coverage-text

Here you can find the multiple options available for code coverage analysis The Command-Line Test Runner.

NOTE: In order to use the code coverage option, we must have Xdebug installed and enabled in our system.

Enough of the theory about code coverage analysis, let me show you how metrics actually look like. The below screenshot is showing code coverage for one of our GitHub repositories.

Here at Sendinblue we follow OOPS. From a code coverage point of view, we mainly focus on Class & Method metrics and try to cover 70% of the code.
We should also keep an eye on how many assertions we have for a test. Ideally, one test should have one assertion but this may lead us towards writing too many test methods. So, it’s ok to have multiple assertions for a single test. Just make sure our test methods don’t try to test too many different things at once. If that’s the case then we should try to split the test method into multiple test methods. IMHO, it’s ok if we are having less than five asserts for a test.

Final Words

“No amount of testing can prove a software right, a single test can prove a software wrong.” — Amir Ghahrai


Senior Software Engineer at Sendinblue