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?
- Reduces Defects in the newly developed features or reduces bugs when changing the existing functionality.
- 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.
Conventions For Writing Unit Tests
Below are major conventions to write tests with PHPUnit.
- The test file should be named as *Test.php, e.g. for file Product.php test file should be ProductTest.php
- Test class (ProductTest) inherits from
- Tests are the methods that should be named as
test*, for e.g. testGetName().Alternatively, you can use the
@testannotation in a method’s DocBlock to mark it as a test method.
- Test methods are `public`.
- Mirror your
src/directory structure in your
NOTE: Above src directory structure is referred from a Symfony project. You may have a completely different code & directory structure.
One can easily install PHPUnit via Composer:-
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
Once you have installed the PHPUnit, create a phpunit.xml file. This is where you configure the specific options for your tests.
- 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:
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.
While writing the tests we want to cover as much as possible, so needs to validate our functionality against a different set of data. In order to make this process simpler, we are blessed with Data Providers.
Rules To Use Data Providers:
- The data provider method returns either an array of arrays or an object that implements the
Iteratorinterface and yields an array for each iteration step.
- Data provider method must be
- 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:
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.
In this article, we have covered how to get started with unit testing using PHPUnit. There are a lot more other important topics like Fixtures, Test Doubles, etc.
Unit testing is excellent and helps us to have a stable code with a lesser number of issues however, it has limitations too. As unit testing only covers isolated testing of the small units of the code, we can’t be sure how these small units of code will behave once they get integrated with each other. So, we should also perform integration testing where individual units/components are combined and tested as a group.
“No amount of testing can prove a software right, a single test can prove a software wrong.” — Amir Ghahrai