The last few things that I want to cover in this series are annotations, coverage reports and show a real life example of how unit tests can help in the real world.

Annotations

Annotations are notes made in a PHPDoc Block before specific test methods that give directions to PHPUnit while running tests. As of this writing, PHPUnit’s webpage lists roughly 28 (plus some variants) assertions. We’ve covered @dataProvider in the last post, I want to cover several more.

Annotation: @covers

Where does it go: PHPDoc Block above the testing method

What does it do: Tells PHPUnit which method the test is covering. When running coverage reports it limits the test’s scope to that method and will not apply coverage to any other method. It’s a good way to keep your coverage report honest. See the section below for more information on coverage reports.

Usage: note the syntax is Class_name::method_name

/**
  * @covers Demo_Plugin::init
  */
function test_init() {

    //codecodecodecodecode...

}

Annotation: @codeCoverageIgnore, @codeCoverageIgnoreStart, @codeCoverageIgnoreEnd

Where does it go: PHPDoc Block above the tested method

What does it do: Tells PHPUnit that this method should not be considered in a coverage report. Some code is impossible or unnecessary to test, this is how you can get accurate measures of your code coverage report. If you do not want to ignore a full method, you can use @codeCoverageIgnoreStart/End

Usage:

/**
  * @codeCoverageIgnore
  */
function get_listing() {

    //codecodecodecodecode...

}

-or-


function get_listing() {

    //codecodecodecodecode...

    /**
      * @codeCoverageIgnoreStart
      */

        //codecodecodecodecode...

    /**
      * @codeCoverageIgnoreEnd
      */

    //codecodecodecodecode...

}

Annotation: @expectedException, @expectedExceptionCode, @expectedExceptionMessage

Where does it go: PHPDoc Block above the testing method

What does it do: These set up expectations for exceptions for test methods. You can be as vague or specific as you want (e.g. just use @expectedExeception vs using @expectedExeception, @expectedExeceptionCode and/or @expectedExeceptionMessage Also note that @expectedExeceptionMessage accepts Regular Expressions, very useful.

Usage: borrowed from PHPUnit.de
Tester file excerpt:

/**
  * @expectedException Exception
  * @expectedExceptionCode 20
  * @expectedExceptionMessage ‘Some Message’
  */
function test_exception() {
    $class = new MyClass
    $class->throwAnException();
}

Tested file:

class MyClass {
    public function throwAnException(){
        throw new Exception('Some Message', 20);
    }
}

Code Coverage Report

When writing test cases, you will encounter conditionals within the methods you are testing that alter the behavior of the method and its return value. That’s where code coverage reports come in handy. PHPUnit, using libraries from Xdebug, can generate these reports for you, in easy to read HTML.

Executing one is just as easy as running tests. At the command line, where you ran all of your tests already, execute phpunit –coverage-html ./coverage. This will run the tests and then generate an HTML coverage report. You can open the index.php file that is generated in the coverage directory in a browser.

Using the tests that were in the gist we notice that we do not have 91.18% coverage, a bit shy of 100% coverage.

coverage-report-resultIf you click into Above is a screenshot from the coverage report for demo-plugin.php you will see that it’s color coded. The categorize_post method has only 85% coverage and everything else is 100%.

Scrolling down you will see green, red and yellow blocks, Green means that the code was executed, Red that it was not executed and Yellow means it’s dead code. Yellow/dead code typically consists of the closing curly braces after return.

We seem to have forgotten (intentionally ;)) to test that if DOING_AUTOSAVE and DOING_AJAX are defined and that the method returns null in these instances and that the method returns null if the post is not a revision. See if you can write the test cases for these and get them to pass. Hey – homework helps us grow!

Sidebar: Learn more about what the CRAP number on coverage reports mean.

Use Case, Why Unit Test

We’re a the point where the plugin is live on our site and working well. It’s test coverage is 100% and functionally performs exactly as expected. Then John Q. Programmer gets hired. John, misguidedly and trying to make a good impression, believes he is optimizing the code by replacing, in the categorize_post method, the conditional if ( strstr( $title, $term_title ) ) {  to use strpos instead of strstr because he thinks its faster (note: it’s not). This comes at a time when the rest of the development team is away and John, not knowing what a unit test is, commits the code and pushes it out to production and pats himself on the back.

Weeks go by and editorial adds a bug ticket. Apparently, not all of the posts are being tagged properly. You wonder and being the savvy (read: OCD) programmer you check out the latest and greatest source and run the tests. Lo and behold, a failure! From the unit test, we immediately see that the post titles that start with the keyword aren’t being tagged as expected. You take a look at the git log and see John’s blunder.

His misguided idea to use strpos instead of strstr not only slows down the process, strpos returns a 0 (falsey) if the needle is in the first (zeroth) position of the post title. This causes the condition to fail and post not to be properly categorized. You revert the commit, give John a reminder on how code needs to be peer reviewed and unit tests must be ran before committing and live happily ever after.

Without a properly written unit test with good test data who knows how long it would have taken to track that bug down.

Now this example is somewhat watered down – but it demonstrates the power of well written unit tests. If you are like most programmers, your refactoring strategy is commit and pray; unit tests, while not a panacea, can greatly ease that anxiety. Like everything else in life, testing comes with time, there are few shortcuts. The bottom line, in this writer’s humble opinion, is the more you write tests, the better the tests become, the more you understand the code, the more confident of a programmer you will become. Enjoy writing tests for your code and gunning for 100% coverage.

[voce-related-posts]