Using data providers (Intermediate)
Data providers are a great way to test many different variants of a single method call quickly. When you have a method that is responsible for applying an algorithm to the method arguments and come up with a predictable result then data providers are a great option.
How to do it...
Modify the contents of test/CardTest.php
to the following:
<?php class CardTest extends PHPUnit_Framework_TestCase { private $card; public function setUp() { $this->card = new Card('4', 'spades'); } public function testGetNumber() { $actualNumber = $this->card->getNumber(); $this->assertEquals(4, $actualNumber, 'Number should be <4>'); } public function testGetSuit() { $actualSuit = $this->card->getSuit(); $this->assertEquals('spades', $actualSuit, 'Suit should be <spades>'); } public function matchingCardDataProvider() { return array( array(new Card('4', 'hearts'), true, 'should match'), array(new Card('5', 'hearts'), false, 'should not match') ); } /** * @dataProvider matchingCardDataProvider */ public function testIsInMatchingSet(Card $matchingCard, $expected, $msg) { $this->assertEquals($expected, $this->card->isInMatchingSet($matchingCard), "<{$this->card->getNumber()} of {$this->card->getSuit()}> {$msg} " . "<{$matchingCard->getNumber()} of {$matchingCard->getSuit()}>"); } }
How it works...
The new matchingCardDataProvider()
method is our data provider. It should return an array containing multiple arrays of arguments to pass into a test method. The method does need to be public as it actually gets called from outside the test case. Also, the method does not have to be static, as you do not have reliable access to any variable you should treat the method as though it were static.
You then need to assign the data provider to one of your test methods. This is done using the @dataProvider
annotation. In this example, the annotation is assigned to the testIsInMatchingSet()
method. You will notice that this method has three parameters. This is exactly the same number of items there are in each sub-array returned by matchingCardDataProvider()
.
The three parameters in this example are the arguments provided for isInMatchingSet()
, an expected value, as well as part of the assertion failure message. When using data providers you can use the Don't Repeat Yourself (DRY) principal very effectively to reduce the amount of code you have to write for each test. However, this does need to be balanced with readability. If you reduce the amount of code that has to be written, but someone else can't understand what the test is doing then the effectiveness and maintainability of the test is actually reduced.
Identifying test failures
You may be wondering how to identify which data set failed while using the data providers. Fortunately, PHPUnit takes care of this for you. Modify the matchingCardDataProvider()
method to return a row that will force the test to fail.
public function matchingCardDataProvider() { return array( array(new Card('4', 'hearts'), true, 'should match'), array(new Card('5', 'hearts'), false, 'should not match'), array(new Card('4', 'clubs'), false, 'should not match') ); }
Then, run the unit test suite and you will see the following:
As you can see it tells you the index of the data set along with the actual parameters passed as a part of that data set.
This can be improved even further by providing keys to the array that your data provider returns. Try using the following data provider:
public function matchingCardDataProvider() { return array( '4 of Hearts' => array(new Card('4', 'hearts'), true, 'should match'), '5 of Hearts' => array(new Card('5', 'hearts'), false, 'should not match'), '4 of Clubs' => array(new Card('4', 'clubs'), false, 'should not match') ); }
Run the tests again to see the following output:
As you can see, you can utilize data providers to not only consolidate your code, but you can also make it very easy to isolate the data set you have problems with.