PHPUnit CodeCoverage
PHPUnit CodeCoverage
There is a ton of information about your code PHPUNit CodeCoverage can provide. At the root just making sure your coverage is at a certain level and if not where is it lacking.
With it’s xml output I use a script found here to check during our TravisCI builds if the code is meeting a required level.
Here is a gist of that script here
and here is the .travis.yml
section snippet that will run the coverage generating test and then review it for percentage of coverage.
script:
- vendor/bin/phpcs --standard=psr2 app/
- set -e
- vendor/bin/phpunit --coverage-clover clover.xml --stop-on-failure --testsuite=Unit,Feature
- php ./coverage-checker.php clover.xml 70
This is what we run in Travis to review the coverage, if it is below 70% it will fail.
If your unit tests cover 100% of your code, you’re doing it wrong. Just my 2 cts. - Fabien Potencier
Setting Up
This can be tricky you need Xdebug in place else you might get “Coverage driver missing” errors.
Once ready you need to update your phpunit.xml
here is my example:
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
/// left out some area for brevity
<testsuites>
<testsuite name="Unit">
<directory suffix="Test.php">./tests/Unit</directory>
</testsuite>
<testsuite name="Feature">
<directory suffix="Test.php">./tests/Feature</directory>
</testsuite>
</testsuites>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">./app</directory>
<exclude>
<directory suffix=".php">app/Http/Controllers/Auth</directory>
<file>app/Console/Commands/SetupDeploymentCommand.php</file>
<file>app/Console/Kernel.php</file>
<file>app/Http/Kernel.php</file>
<file>app/Providers/AppServiceProvider.php</file>
<file>app/Providers/AuthServiceProvider.php</file>
<file>app/Providers/EventServiceProvider.php</file>
<file>app/Providers/BroadcastServiceProvider.php</file>
<file>app/Providers/RouteServiceProvider.php</file>
<file>app/Http/Controllers/Controller.php</file>
<file>app/Exceptions/Handler.php</file>
<file>app/Http/Middleware/Authenticate.php</file>
<file>app/Http/Middleware/EncryptCookies.php</file>
<file>app/Http/Middleware/RedirectIfAuthenticated.php</file>
<file>app/Http/Middleware/TrimStrings.php</file>
<file>app/Http/Middleware/TrustProxies.php</file>
<file>app/Http/Middleware/VerifyCsrfToken.php</file>
</exclude>
</whitelist>
</filter>
/// left out some area for brevity
</phpunit>
Above I setup filter
to exclude a bunch of Laravel files I do not want to maintain.
Running the Command
vendor/bin/phpunit --coverage-clover clover.xml --stop-on-failure --coverage-html=build
This will output the clover.xml and make a folder called build
make sure to include that in your .gitignore
On my Mac, when done, I just type open build/index.html
and I go to a page like this
Exploring
As seen above I get a list of all my classes and how the coverage is on each them. It gets even better from here.
Class Level
So you are wondering why a class is missing coverage? Click on the Class folder and start to dig in.
Maybe it finds something you forgot or maybe it is fussing about a bit of code you know you tested?
In this example it could be either. So I have two main options.
Annotate the test
Let’s say I have a Controller level test that is testing this:
/**
* @covers App\Http\Controllers\AdminController::index
*/
public function testWorksAsAdmin()
{
$user = factory(\App\User::class)->states('admin')->create();
$this->be($user);
$this->get("/admin")->assertStatus(200)->assertSee("Show user");
}
/**
* @covers App\Http\Controllers\AdminController::index
*/
public function testWorksAsNonAdmin()
{
$user = factory(\App\User::class)->create();
$this->be($user);
$this->get("/admin")->assertStatus(302);
}
You can see I specifically tell PHPUnit CodeCoverage that this controller tests are testing this area. But I do not let it know about the MiddleWare so I could do:
/**
* @covers App\Http\Controllers\AdminController::index
* @covers App\Http\Controllers\AdminController::__constructor
*/
public function testWorksAsAdmin()
{
$user = factory(\App\User::class)->states('admin')->create();
$this->be($user);
$this->get("/admin")->assertStatus(200)->assertSee("Show user");
}
/**
* @covers App\Http\Controllers\AdminController::index
* @covers App\Http\Controllers\AdminController::__constructor
*/
public function testWorksAsNonAdmin()
{
$user = factory(\App\User::class)->create();
$this->be($user);
$this->get("/admin")->assertStatus(302);
}
Then run the test command again and:
Annotate the Class
In the above I could have just done this:
/**
* @codeCoverageIgnore
*/
public function __construct()
{
$this->middleware('admin_only')->only("index");
}
And that is fine, as long as you know that middleware is tested in it’s own area.
Exceptions
I tend to do try/catch in my controllers to clearly react to an issue and respond to a user’s request but this can show in PHPUnit Coverage as a non tested section of code.
All I need to do is add a test for that.
/**
* @covers App\Http\Controllers\AdminController::index
* @covers App\Http\Controllers\AdminController::__constructor
* @expectedException \Exception
*/
public function testWorksAsNonAdmin()
{
$user = factory(\App\User::class)->create();
$this->be($user);
$this->get("/admin")->assertStatus(422);
}
Dashboard
This is where some other nice info is surfaced about your code.
Should bring you to this layout
The CRAP rating is “interesting” you can read more about it here since I do not fully understand it myself but imo it comes down not to 100% coverage but:
- Keep classes small and focused
- Keep classed down to 1 public method, even Controllers letting other protected/private methods do the work
- Test the class for message in and message out
“You don’t send messages because you have objects, you have objects because you send messages - Sandi Metz Poodr Book”
Helpers
Two other tools to compliment that above
PHPMetrics
This tool http://www.phpmetrics.org/ can easily be setup and run showing more details about your classes and their state of “complexity” etc.
LaraStan
This tool https://github.com/nunomaduro/larastan when it does not go overboard in it’s reading your code (set it to a low level) can do a good job of finding issues in your code that you did not find in your tests. It runs over your code including exceptions you did not cover in your tests etc. Really does find some interesting things.
comments powered by Disqus