Book Image

Instant Hands-on Testing with PHPUnit How-to

By : Michael Lively
Book Image

Instant Hands-on Testing with PHPUnit How-to

By: Michael Lively

Overview of this book

No developer wants to accept the inherent difficulty of writing software as an excuse for not finding the bugs in our code before anyone else does. PHPUnit is a framework that was created to allow developers to solve that very problem. It provides a feature-rich environment with most of the tools necessary to provide adequate tests for any project. "Instant Hands-on Testing with PHPUnit How-to" provides a thorough overview of the functionality provided by the PHPUnit framework. It shows how the plethora of features in the framework can be used to write tests for real world projects to ensure they function and will continue to function in the ways that you expect. This book will show how you can set up the scaffolding necessary to run unit tests in your project with PHPUnit. It will walk you through the process of how to write a basic test and how to maintain your project's test suite. You will learn how to use some of the more advanced features of PHPUnit and then see how you can use mock objects to isolate the code you are testing. We will then discover how to create tests that verify your interaction with databases and even see how you can use PHPUnit to understand which code you are actually testing. At the end of the book you will have all of the basic understanding necessary to begin adding tests to your project. This book provides a great foundation for becoming a expert at writing unit tests.
Table of Contents (7 chapters)

Generating tests from code (Advanced)


When you are writing tests for untested legacy code or you do not employ a test-driven development methodology you will frequently find yourself needing to create test cases for already existing classes. PHPUnit has built-in capability to create skeletons for existing classes. This functionality can help you build up your test suite very quickly.

Using this functionality to test legacy code can be very effective. It will create several tests that are marked as incomplete which can be used to help you determine how far away you are from having coverage in all of your class methods.

Getting ready

The skeleton functionality is an add-on that must be installed to PHPUnit. It can be installed via PEAR using the phpunit/PHPUnit_SkeletonGenerator package.

In order for the preceding command to work you do need to make sure the auto_discover configuration is set to 1. If you get errors about unrecognized channels you can enable auto_discover with the sudo pear config-set auto_discover 1 command.

If you are using Composer in your project, it is worth noting that there is not a composer package for the Skeleton Generator. You will have to install it manually.

How to do it...

  1. Run phpunit-skelgen --test -- Player src/Player.php PlayerTest test/PlayerTest.php in the project folder.

  2. Open the test/PlayerTest.php file. You will see the following code in that file:

    <?php
    /**
     * Generated by PHPUnit_SkeletonGenerator 1.2.0 on 2013-01-01 at 23:02:55.
     */
    class PlayerTest extends PHPUnit_Framework_TestCase
    {
        /**
         * @var Player
         */
        protected $object;
    
        /**
         * Sets up the fixture, for example, opens a network connection.
         * This method is called before a test is executed.
         */
        protected function setUp()
        {
            $this->object = new Player;
        }
    
        /**
         * Tears down the fixture, for example, closes a network connection.
         * This method is called after a test is executed.
         */
        protected function tearDown()
        {
        }
    
        /**
         * @covers Player::getName
         * @todo   Implement testGetName().
         */
        public function testGetName()
        {
            // Remove the following lines when you implement this test.
            $this->markTestIncomplete(
              'This test has not been implemented yet.'
            );
        }
    
        /**
         * @covers Player::drawCard
         * @todo   Implement testDrawCard().
         */
        public function testDrawCard()
        {
            // Remove the following lines when you implement this test.
            $this->markTestIncomplete(
              'This test has not been implemented yet.'
            );
        }
        //...Rest of tests
    }

How it works...

The phpunit-skelgen command takes up to four parameters. The format of the command is phpunit-skelgen --test -- <Class Name> <Class File Path> <Test Class Name> <Test Class File Path>. The class names should be fully qualified class names including the namespace. There are variants of this command that you can use that will look for the file based on the name; however, being as explicit with this command as possible will give you better, more predictable results.

One thing that you will notice is that the skeleton does not properly invoke your constructor. You have to handle this piece of the test case manually. Once that is done you will see that all of the tests return Incomplete as the status. As you fill out the tests they will change from incomplete tests to passing tests.

There's more...

The PHPUnit Skeleton Generator is a very powerful command. So far we have barely scratched the surface of how you can use it. When you combine it with PHPDoc annotations you can generate some of the actual test code as opposed to incomplete stubs. You can also use the PHPUnit Skeleton Generator to assist in test-driven development.

Using @assert to generate additional code

You can give the generator hints as to what test code should be created using the @assert annotation. These annotations should be added directly to the code that you will be testing. The format of the annotation is @assert (arg1, arg2, …, argn) operation result. The operation can be any logical comparison such as ==, !=, <, or > as well as the throws string. The logical comparisons are obvious in their use. The == operation is the equivalent to the PHPUnit assertEquals() method. The throws operator is equivalent to the @expectedException annotation.

You can see this in action by adding the following comment to the isInMatchingSet() method in src/Card.php:

  /**
   * Returns true if the given card is in the same set
   * @param Card $card
   * @return bool
   * @assert (new Card(3, 'h'), new Card(3, 's')) == true
   * @assert (new Card(4, 'h'), new Card(3, 's')) == false
   */
  public function isInMatchingSet(Card $card)

When you run the phpunit-skelgen --test -- Card src/Card.php CardTest2 test/CardTest2.php command and look at the generated test/CardTest2.php file you will now see the following test method:

    /**
     * Generated from @assert (new Card(3, 'h'), new Card(3, 's')) == true.
     *
     * @covers Card::isInMatchingSet
     */
    public function testIsInMatchingSet()
    {
        $this->assertTrue(
          $this->object->isInMatchingSet(new Card(3, 'h'), new Card(3, 's'))
        );
    }

    /**
     * Generated from @assert (new Card(4, 'h'), new Card(3, 's')) == false.
     *
     * @covers Card::isInMatchingSet
     */
    public function testIsInMatchingSet2()
    {
        $this->assertFalse(
          $this->object->isInMatchingSet(new Card(4, 'h'), new Card(3, 's'))
        );
    }

You'll notice that for each @assert annotation, a corresponding test method was created.

Using the Skeleton Generator for test-driven development

The Skeleton Generator can also be used when employing a test-driven development methodology. The examples so far have been focused on creating tests based on written code. This is contradictory to the test-driven development methodology. However, you can create code from tests just as easily as you can create tests from code. When you run phpunit-skelgen --class – CardTest test/CardTest.php from the project directory you will see that it creates a new Card class in src/Card.php. It even stubs the methods that it detects based on the test methods you wrote.

<?php
/**
 * Generated by PHPUnit_SkeletonGenerator 1.2.0 on 2013-02-11 at 00:12:00.
 */
class Card
{
    /**
     * @todo Implement getNumber().
     */
    public function getNumber()
    {
        // Remove the following line when you implement this method.
        throw new RuntimeException('Not yet implemented.');
    }

    /**
     * @todo Implement getSuit().
     */
    public function getSuit()
    {
        // Remove the following line when you implement this method.
        throw new RuntimeException('Not yet implemented.');
    }

    /**
     * @todo Implement isInMatchingSet().
     */
    public function isInMatchingSet()
    {
        // Remove the following line when you implement this method.
        throw new RuntimeException('Not yet implemented.');
    }
}