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)

Using test dependencies (Advanced)


When you begin writing tests for one of your classes you may notice that when one aspect of functionality for your class breaks, many tests fail. Quite often a method of a class will have some preconditions that must be true for it to behave properly in a given situation. A classic example of this is a stack. If you cannot construct a stack properly then any further tests against that stack are most likely going to fail.

You can use PHPUnit's test dependency feature to help with this. When you indicate that one test is dependent on another test, PHPUnit will skip the dependent test whenever its dependencies do not successfully pass. Test dependencies also allow you to enable producer-consumer relationships into your test suites. One test case will "produce" the input for another test case to "consume".

We will take a look at how test dependencies can work by writing a test for our CardCollection class that looks at how cards are added to the deck.

How to do it...

Place the following code to the test/CardCollection.php file:

<?php
class CardCollectionTest extends PHPUnit_Framework_TestCase
{
  private $cardCollection;

  public function setUp()
  {
    $this->cardCollection = new CardCollection();
  }

  public function testCountOnEmpty()
  {
    $this->assertEquals(0, $this->cardCollection->count());
  }

  /**
   * @depends testCountOnEmpty
   */
  public function testAddCard()
  {
    $this->cardCollection->addCard(new Card('A', 'Spades'));
    $this->cardCollection->addCard(new Card('2', 'Spades'));

    $this->assertEquals(2, $this->cardCollection->count());

    return $this->cardCollection;
  }

  /**
   * @depends testAddCard
   */
  public function testGetTopCard(CardCollection $cardCollection)
  {
    $card = $cardCollection->getTopCard();

    $this->assertEquals(new Card('2', 'Spades'), $card);
  }
}

How it works...

In your new file you have two test methods using a @depends annotation. This is the annotation that enables PHPUnit's test dependency functionality. This annotation, essentially, tells PHPUnit that you do not want to run the following test unless the test referenced in the @depends annotation has passed. If this test has not passed then the following test will be skipped. If for some reason the CardCollection::count() method was not running properly and caused the testCountOnEmpty() test to fail then testAddCard() would be skipped. This can be easily seen by breaking the testCountOnEmpty() test on purpose by inserting $this|fail('testing @depends') in the test and rerunning your tests.

Another interesting aspect of the @depends annotation is the producer-consumer aspect of it. Whenever you mark a test with the @depends annotation the return value from the test specified in the annotation will be provided as the argument to the test being annotated. This is what is happening in the testGetTopCard() method. The testAddCard() method returns the card collection being tested. This value then persists for any test that depends on this method. As soon as we annotated testGetTopCard() with @depends testGetTopCard, PHPUnit is triggered to pass the populated card collection as the first parameter.

This does a couple things for you. It doesn't bother to try and pull the top card if it appears that addCard() is not working. It also prevents you from having to repeat the code necessary to populate your card collection.

Another thing to note is that the @depends annotations always reference a test above the annotation. The @depends annotation never influences the order of tests. Tests will always be run from the top of the file to the bottom of the file. If the @depends annotation references a method below the annotation it will simply skip the test as the dependency has not yet passed.

Multiple test dependencies

You can add multiple @depends annotations to a single test. PHPUnit will then check to ensure that all of the tests specified have passed before running a given test. If the dependencies also return values, they will all be accessible as arguments in the order they are specified. The following code shows how this works:

<?php
class DependencyTest extends PHPUnit_Framework_TestCase
{

  public function test1()
  {
    $this->assertTrue(true);
    return 1;
  }

  public function test2()
  {
    $this->assertTrue(true);
    return 2;
  }

  public function test3()
  {
    $this->assertTrue(true);
    return 3;
  }

  /**
   * @depends test1
   * @depends test2
   * @depends test3
   */
  public function testDependencies($arg1, $arg2, $arg3)
  {
    $this->assertEquals(1, $arg1);
    $this->assertEquals(2, $arg2);
    $this->assertEquals(3, $arg3);
  }
}

Multiple dependent tests

You can also have the same test referenced by @depends multiple times. You do need to be very careful when doing this. Copies are not made of any objects returned. So if you modify the object in any way in the first dependent test, those modifications will also be present in the second dependent test. This can be seen in the following code:

<?php
class DependencyTest extends PHPUnit_Framework_TestCase
{

  public function testCreateStdClass()
  {
    $obj = new stdClass();
    $obj->foo = 'bar';
    $this->assertTrue(true);
    return $obj;
  }

  /**
   * @depends testCreateStdClass
   */
  public function testDependency1($obj)
  {
    $this->assertEquals('bar', $obj->foo);
    $obj->foo = 'notbar';
  }

  /**
   * @depends testCreateStdClass
   */
  public function testDependency2($obj)
  {
    $this->assertEquals('notbar', $obj->foo);
  }
}