Quality Assurance on PHP projects – PHPUnit lessons learned
And I believe Lars Tesmer has done the latter, where he blogs on testing PHPUnit itself and the lessons learned in that process.
Thank you Lars for sharing your lessons.
And I believe Lars Tesmer has done the latter, where he blogs on testing PHPUnit itself and the lessons learned in that process.
Thank you Lars for sharing your lessons.
In this fourth part we should focus on playing the game. Let’s go over the steps again:
So this is a simple and a straightforward rule we can turn into code. But let’s look at the tests we’ve written in part 1 to see if they’re still valid.
public function testGameCanBePlayed() { $playerX = $this->_ttt->getPlayers()->seek(0)->current(); $playerO = $this->_ttt->getPlayers()->seek(1)->current();
$this->assertFalse($this->_ttt->play(0, 0, $playerX)); $this->assertFalse($this->_ttt->play(0, 1, $playerX)); $this->assertTrue($this->_ttt->play(0, 2, $playerX));
$this->_ttt->setGrid(new Grid());
$this->assertFalse($this->_ttt->play(0, 0, $playerX)); $this->assertFalse($this->_ttt->play(1, 0, $playerX)); $this->assertTrue($this->_ttt->play(2, 0, $playerX));
$this->_ttt->setGrid(new Grid());
$this->assertFalse($this->_ttt->play(0, 0, $playerX)); $this->assertFalse($this->_ttt->play(1, 1, $playerX)); $this->assertTrue($this->_ttt->play(2, 2, $playerX));
$this->_ttt->setGrid(new Grid());
$this->assertFalse($this->_ttt->play(0, 2, $playerX)); $this->assertFalse($this->_ttt->play(1, 1, $playerX)); $this->assertTrue($this->_ttt->play(2, 0, $playerX)); }
As you see, we only tested the functionality to see if we can have a winner when 3 identical symbols appear in a single row horizontal, vertical or diagonal. We can make a couple of comments on this test:
In other words, we need to clean this up and come up with better tests! Let’s focus on tearing this test into 3 separate tests, testing just a single direction. Again, we can use a dataProvider method for this.
public function rowColProvider() { return array ( array (array (array (0,0), array (0,1), array (0,2))), array (array (array (0,0), array (1,0), array (2,0))), array (array (array (0,0), array (1,1), array (2,2))), array (array (array (0,2), array (1,1), array (2,0))), ); } /** * @dataProvider rowColProvider */ public function testGameplayCanDetectWinner($rowCols) { $player = $this->_ttt->getPlayers()->seek(0)->current(); $this->assertFalse($this->_ttt->play($rowCols[0][0], $rowCols[0][1], $player)); $this->assertFalse($this->_ttt->play($rowCols[1][0], $rowCols[1][1], $player)); $this->assertTrue($this->_ttt->play($rowCols[2][0], $rowCols[2][1], $player)); }
Now we know that with each turn, the return value will indicate if we have a winner (TRUE) or not (FALSE). But we still need to see if we can have the same result when playing with two players. As you see with the last line, the third entry gives us a positive result.
But we still don’t have a gameplay going! We need to have two players enter the arena and each player playing turn by turn. So how do we approach this? Well, the easiest way for now is to create a test that does just that. The benefit here is we can decide which player is going to win the game.
public function testGameCanBePlayed() { $playerX = $this->_ttt->getPlayers()->seek(0)->current(); $playerO = $this->_ttt->getPlayers()->seek(1)->current(); $this->assertFalse($this->_ttt->play(0, 0, $playerX)); $this->assertFalse($this->_ttt->play(0, 1, $playerO)); $this->assertFalse($this->_ttt->play(1, 1, $playerX)); $this->assertFalse($this->_ttt->play(2, 2, $playerO)); $this->assertFalse($this->_ttt->play(1, 0, $playerX)); $this->assertFalse($this->_ttt->play(2, 0, $playerO)); $this->assertTrue($this->_ttt->play(1, 2, $playerX)); }
Visual this result looks like the following grid:
X | O | X | X | X O | | O
But most importantly our tests are still green, giving us that wonderful feeling of achievement.
Let’s finish up our tests so we can see no one can play any further after we’ve got a winner. PHPUnit provides a nice anotation we can use for this: depends. So now we can test that the game is stopped, depending on our test “testGameCanBePlayed”.
For this to work, we need to return our game (in this case $this->_ttt). Just add the following line at the bottom of the test class “testGameCanBePlayed”.
public function testGameCanBePlayed() { $playerX = $this->_ttt->getPlayers()->seek(0)->current(); $playerO = $this->_ttt->getPlayers()->seek(1)->current(); $this->assertFalse($this->_ttt->play(0, 0, $playerX)); $this->assertFalse($this->_ttt->play(0, 1, $playerO)); $this->assertFalse($this->_ttt->play(1, 1, $playerX)); $this->assertFalse($this->_ttt->play(2, 2, $playerO)); $this->assertFalse($this->_ttt->play(1, 0, $playerX)); $this->assertFalse($this->_ttt->play(2, 0, $playerO)); $this->assertTrue($this->_ttt->play(1, 2, $playerX)); return $this->_ttt; }
And the next step is simple:
/** * @depends testGameCanBePlayed * @expectedException RuntimeException */ public function testGameStopsAfterWinning($game) { $playerO = $game->getPlayers()->seek(1)->current(); $game->play(2,1, $playerO); }
In order for this test to succeed we need to add a couple of things to our game:
<?php...
class Tictactoe{ ...
/** * Status indicator to say there's a winner or not * * @var bool */ protected $_winner = false;
...
/** * Sets a flag to indicate this game has a winner * * @param bool $flag * @return Tictactoe */ public function setWinner($flag = true) { $this->_winner = $flag; return $this; } /** * Checks if the game has a winner * * @return bool */ public function hasWinner() { return $this->_winner; } /** * Plays the game and returns TRUE if a player has become a winner, FALSE * if the player is not (yet) a winner. * * @param int $row * @param int $column * @param Player $player * @return bool * @throws RuntimeException */ public function play($row, $column, Player $player) { if ($this->hasWinner()) { throw new RuntimeException('Game already has a winner'); } $this->getGrid()->setSymbol($row, $column, $player->getSymbol()); return $this->isWinner($player); } /** * Returns TRUE if a player has become a winner, FALSE if not. * * @param Player $player * @return bool */ public function isWinner(Player $player) { if ($this->getGrid()->inRow($player->getSymbol())) { $this->setWinner(); return true; } if ($this->getGrid()->inColumn($player->getSymbol())) { $this->setWinner(); return true; } if ($this->getGrid()->inDiagonal($player->getSymbol())) { $this->setWinner(); return true; } return false; }}
That’s it. We covered the main purpose of the game and we did it semi test driven. We can start playing a cute little game of Tictactoe and covered a couple important features of PHPUnit. I also showed that it’s not a bad thing if you modify parts of your tests to achieve new or other specifications.
The full game source code can be found on my github account (https://github.com/DragonBe/tictactoe), and as you go over the log, you can follow along with these series as well. When playing with the source code, you might think about testing edge cases: playing 9 rounds and no winner, try to place a symbol off the grid, place twice a symbol on the grid of a single player (cheating), and so on.
Another thing could be that you turn this little game into an online game. What are the things you need to concider in that situation. Are our tests still valid or do we need to modify our architecture and our tests to support that kind of game playing?
Since it’s on GitHub, you can fork it and send me a pull request once you have an edge case figured out.
When we look at our initial tests for players, we only focussed on two things: each player has a symbol and players are managed within a collection class.
public function testGamePlayersAreSetAtStart() { $players = $this->_ttt->getPlayers(); $this->assertInstanceOf('Players', $players); $this->assertEquals(2, count($players)); $this->assertEquals(Player::PLAYER_X, $players->seek(0)->current()->getSymbol()); $this->assertEquals(Player::PLAYER_O, $players->seek(1)->current()->getSymbol()); }
As you can imagine, this is a very minimalistic test that cannot cope with all player related issues. We did have two classes to handle all player related issues and thus we need to move our test out of our TictactoeTest class.
A quick look at the functionality of a player should give us more fuel to creat better unit tests. Bytaking a new look at the functionality of a single player, we can optimize our tests. Since we already have some working code, let’s take a quick look at the Player class to figure out what is being expected.
<?php/** * TicTacToe * * A simple game that's played with two players, each taking a turn by marking * a field in a grid of 3 x 3 with either an X or an O (one symbol per player). * Winner is the one who has 3 identical symbols in a single horizontal, * vertical or diagonal row. * * @package Tictactoe * @license "Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" * @link http://creativecommons.org/licenses/by-sa/3.0/ *//** * Player * * This Player class is responsible to set up and maintain a single player * * @package Tictactoe * @category Tictactoe * */class Player{ /** * Defines the symbol X for a player to choose * * @var string */ const PLAYER_X = 'X'; /** * Defines the symbol X for a player to choose * * @var string */ const PLAYER_O = 'O'; /** * The symbol a player chooses * * @var string */ protected $_symbol; /** * Constructor for a single player, with an optional symbol (X or O) to * define the player's playing symbol * * @param string $symbol */ public function __construct($symbol = null) { if (null !== $symbol) { $this->setSymbol($symbol); } } /** * Sets a symbol for this Player * * @param string $symbol * @return Player */ public function setSymbol($symbol) { $this->_symbol = (string) $symbol; return $this; } /** * Retrieves the chosen symbol from this Player * * @return string */ public function getSymbol() { return $this->_symbol; }}
As you can see, we only care about the symbol of the player. Apparently this is more than enough for now. But when we check our main TictactoeTest we see no specific tests for our Player class. About time to do something about it.
<?phpclass PlayerTest extends PHPUnit_Framework_TestCase{ protected $_player; protected function setUp() { $this->_player = new Player(); parent::setUp(); } protected function tearDown() { $this->_player = null; parent::tearDown(); } public function testPlayerHasNoSymbolAtStartup() { $this->assertNull($this->_player->getSymbol()); } public function goodSymbolProvider() { return array ( array ('O'), array ('X'), ); } /** * @dataProvider goodSymbolProvider */ public function testPlayerCanSetASymbol($symbol) { $this->_player->setSymbol($symbol); $this->assertEquals($symbol, $this->_player->getSymbol()); }}
In our code and in our tests we only set a symbol, but don’t check if the symbol is valid. The rules of this game do specify that only a X or a O are allowed. So we need to add additional logic to our tests and code. Let’s work on our test first.
public function badSymbolProvider() { return array ( array ('a'), array (123), array ('Hello World!'), array (0x123), ); } /** * @dataProvider badSymbolProvider * @expectedException InvalidArgumentException */ public function testPlayerThrowsExceptionWithBadSymbol($symbol) { $this->_player->setSymbol($symbol); }
By using the ‘expectedException‘ annotation we can verify that a specific exception is thrown. For most exceptions you can often use one of the SPL exceptions. We choose ‘InvalidArgumentException‘ as the symbol is an invalid argument for this Player class. Of course our tests will fail as we haven’t forseen this feature or behaviour.

We need to modify our code so it can be throw an InvalidArgumentException when wrong arguments (read symbols) are being provided. We want our tests to succeed, right?
/** * Sets a symbol for this Player * * @param string $symbol * @return Player * @throws InvalidArgumentException */ public function setSymbol($symbol) { $validSymbols = array (self::PLAYER_O, self::PLAYER_X); if (!in_array($symbol, $validSymbols)) { throw new InvalidArgumentException( 'Player can only choose between X or O'); } $this->_symbol = (string) $symbol; return $this; }
As you can see, it’s just a small but significant change that ensures proper symbols are being used in our game. And our tests now succeed.

We also have a collection class called Players that contain both players. We could have used an array, but as we don’t know yet what kind of functionality we want for all players. In our code we just created this collection that implements the SeekableIterator and Countable interfaces. Our PlayersTest should be able to add a Player object, even two Player objects but certainly not more than 2 players. In test code this becomes a simple straightforward test.
<?phpclass PlayersTest extends PHPUnit_Framework_TestCase{ protected $_players;
protected function setUp() { $this->_players = new Players(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_players = null; } public function testPlayersIsEmptyAtStartUp() { $this->assertEmpty($this->_players->getPlayers()); } public function testPlayerCanAddPlayer() { $this->_players->addPlayer(new Player()); $this->assertEquals(1, count($this->_players)); } public function testPlayerCanTwoPlayerObjects() { $this->_players->addPlayer(new Player()) ->addPlayer(new Player()); $this->assertEquals(2, count($this->_players)); } /** * @expectedException OverflowException */ public function testPlayerCannotAddMoreThenTwoPlayerObjects() { $this->_players->addPlayer(new Player()) ->addPlayer(new Player()) ->addPlayer(new Player()); }}
Since we implement 2 interfaces we know the functionality of them, and therefor we should not test for it as they will throw errors if not implemented correctly. If you still doubt yourself on working with these interfaces, create a separate set of tests that just verifies your collection class is behaving as expected. Our collection class Players will look like this.
<?php/** * TicTacToe * * A simple game that's played with two players, each taking a turn by marking * a field in a grid of 3 x 3 with either an X or an O (one symbol per player). * Winner is the one who has 3 identical symbols in a single horizontal, * vertical or diagonal row. * * @package Tictactoe * @license "Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" * @link http://creativecommons.org/licenses/by-sa/3.0/ *//** * Players * * This Players class is a collection container for both players, implementing * both Countable and SeekableIterator interfaces from SPL * * @package Tictactoe * @category Tictactoe * @see SeekableIterator * @see Countable * @link http://php.net/spl * */class Players implements SeekableIterator, Countable{ const MAX_PLAYERS = 2; /** * Contains Player objects * * @var array */ protected $_players = array (); /** * Specifies the position in the stack * * @var int */ protected $_position; /** * Keeps track of the count * * @var int */ protected $_count; /** * Constructor for this Players collection, allows setting players with * optional provided array of Player objects * * @param array $array */ public function __construct(array $array = null) { if (null !== $array) { foreach ($array as $player) { $this->addPlayer($player); } } } /** * Adds a Player object to the collection * * @param Player $player * @return Players * @throw Overflow */ public function addPlayer(Player $player) { if (self::MAX_PLAYERS <= $this->count()) { throw new OverflowException('Cannot add more Player objects'); } $this->_players[] = $player; return $this; } /** * Retrieves a list of Player objects currently in the collection * * @return array */ public function getPlayers() { return $this->_players; } /** * Sets the internal stack pointer back to 0 * * @see SeekableIterator::rewind() */ public function rewind() { $this->_position = 0; } /** * Sets the internal stack pointer to the next position * * @see SeekableIterator::next() */ public function next() { $this->_position++; } /** * Checks if the current position in the stack is still valid * * @see SeekableIterator::valid() * @return boolean */ public function valid() { return isset ($this->_player[$this->_position]); } /** * Retrieves the current object from the stack * * @see SeekableIterator::current() * @return Player */ public function current() { return $this->_players[$this->_position]; } /** * Retrieves the current position from the stack * * @see SeekableIterator::key() * @return int */ public function key() { return $this->_position; } /** * Moves the position to the given stack postion * * @see SeekableIterator::seek() * @throws OutOfBoundsException */ public function seek($position) { if (!isset ($this->_players[$position])) { throw new OutOfBoundsException('No more items in this stack'); } $this->_position = $position; return $this; } /** * Keeps record of the count in this collection * * @see Countable::count() */ public function count() { return count($this->_players); }}
Ok, we now have our Grid and our Player objects tested. Next up is testing the game itself. Are all our tests taking care of all rules defined by the game? What are the edge cases? What is not acceptable? All this and more in the next eppisode of ‘Quality Assurance on PHP projects‘.
Our tests tell us we have four classes:
Our first test concerns all about the grid we’re going to use. Here’s the test again:
public function testGameGridIsSetAtStart() { $grid = $this->_ttt->getGrid(); $this->assertInstanceOf('Grid', $grid); $this->assertEquals(3, count($grid->getRows())); foreach ($grid->getRows() as $row) { $this->assertInternalType('array', $row); $this->assertEquals(3, count($row)); $this->assertNull($row[0]); $this->assertNull($row[1]); $this->assertNull($row[2]); } }
What can we learn from this test?
We can also make the following assumptions:
With this information we can start writing our Grid class
<?php/** * TicTacToe * * A simple game that's played with two players, each taking a turn by marking * a field in a grid of 3 x 3 with either an X or an O (one symbol per player). * Winner is the one who has 3 identical symbols in a single horizontal, * vertical or diagonal row. * * @package Tictactoe * @license "Attribution-ShareAlike 3.0 Unported (CC BY-SA 3.0)" * @link http://creativecommons.org/licenses/by-sa/3.0/ *//** * Grid * * This Grid class is responsible to set up and maintain the playing field * * @package Tictactoe * @category Tictactoe * */class Grid{ /** * Constant to define the number of rows * @var int */ const ROWS = 3; /** * Constant to define the number of colomns * @var int */ const COLS = 3; /** * Container for all rows and columns * * @var array */ protected $_rows = array (); /** * Constructor for this Grid class that will set up our grid with 3 rows * and 3 columns while setting the value of each field to NULL */ public function __construct() { for ($i = 0; $i < self::ROWS; $i++) { $columns = array (); for ($j = 0; $j < self::COLS; $j++) { $columns[$j] = null; } $this->addRow($columns); } } /** * Adds a row to the grid, requiring an array of 3 fields representing * the columns * * @param array $row * @return Grid */ public function addRow(array $row) { $this->_rows[] = $row; return $this; } /** * Retrieves all rows from this grid, including an array for the columns * for each row * * @return array */ public function getRows() { return $this->_rows; } /** * Sets the symbol for each field * * @param int $row The position of the field in the row * @param int $column The position of the field in the column * @param string $symbol */ public function setSymbol($row, $column, $symbol) { $this->_rows[$row][$column] = $symbol; return $this; } /** * Retrieves the current symbol from a given cordinate on the grid * * @param int $row The postion of the field in the row * @param int $column The position of the field in the column * @return string */ public function getSymbol($row, $column) { return $this->_rows[$row][$column]; } /** * Validation method to verify if a given symbol is found 3 times in a * single row. If we have 3 matches, it will return TRUE. In all other * cases it will return FALSE. * * @param string $symbol * @return boolean */ public function inRow($symbol) { foreach ($this->getRows() as $row) { $match = 0; foreach ($row as $column) { if ($symbol === $column) { $match++; } } if (self::ROWS === $match) { return true; } } return false; } /** * Validation method to verify if a given symbol is found 3 times in a * single column. If we have 3 matches, it will return TRUE. In all other * cases it will return FALSE. * * @param string $symbol * @return boolean */ public function inColumn($symbol) { for ($i = 0; $i < self::COLS; $i++) { $match = 0; for ($j = 0; $j < self::ROWS; $j++) { if ($symbol === $this->_rows[$j][$i]) { $match++; } } if (self::COLS === $match) { return true; } } return false; } /** * Validation method to verify if a given symbol is found 3 times in a * single diagonal row. If we have 3 matches, it will return TRUE. In all * other cases it will return FALSE. * * @param string $symbol * @return boolean */ public function inDiagonal($symbol) { $match1 = $match2 = 0; for ($i = 0; $i < self::ROWS; $i++) { if ($symbol === $this->_rows[$i][$i]) { $match1++; } if ($symbol === $this->_rows[$i][self::COLS - 1 - $i]) { $match2++; } } if (self::ROWS === $match1 || self::ROWS === $match2) { return true; } return false; }}
Now we have a grid class, but we cannot test it properly as we use a Tictactoe class to validate everything. No problem, we can modify our tests as well to better serve our needs. Actually, we already have defined our test criteria. Let’s list them again:
The first four criterea we already addressed in our general Tictactoe test case. To separate responsibilities, we can move our test case from our main TictactoeTest class into a GridTest class.
<?phpclass GridTest extends PHPUnit_Framework_TestCase{ const TEST_SYMBOL = 'X';
protected $_grid;
protected function setUp() { $this->_grid = new Grid(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_grid = null; } public function testGameGridIsSetAtStart() { $this->assertEquals(Grid::ROWS, count($this->_grid->getRows())); foreach ($this->_grid->getRows() as $row) { $this->assertInternalType('array', $row); $this->assertEquals(Grid::COLS, count($row)); foreach ($row as $column) { $this->assertNull($column); } } }}
So now we have exactly the same as before, except we created a separate TestCase specifically designed for Grid related tests. This allows us to follow up on issues people might report later on (things we haven’t thought of or just don’t know yet), as we can write now a specific test for our Grid.
But we’re not there yet, we still have 2 more things to verify: positioning of a symbol and verifying we have a 3 identical symbols in any of the three rows, columns or diagonal rows.
Let’s look at our test verifying the positioning of a symbol:
public function testGridCanPositionASymbol() { $this->_grid->setSymbol(0, 0, self::TEST_SYMBOL); $this->assertNotNull($this->_grid->getSymbol(0,0)); $this->assertEquals(self::TEST_SYMBOL, $this->_grid->getSymbol(0,0)); }
But as you see, this is not really working for us. We can only ferify just one position on the grid and if we want to test more positions, our test method would grow exponentionally. Say hello to the “dataProvider”
public function cordinateProvider() { return array ( array (0,0), array (0,1), array (0,2), array (1,0), array (1,1), array (1,2), array (2,0), array (2,1), array (2,2), ); } /** * @dataProvider cordinateProvider */ public function testGridCanPositionASymbol($row, $column) { $this->_grid->setSymbol($row, $column, self::TEST_SYMBOL); $this->assertNotNull($this->_grid->getSymbol($row, $column)); $this->assertEquals(self::TEST_SYMBOL, $this->_grid->getSymbol($row, $column)); }
The @dataProvider tag in your codeblock comment tells PHPUnit to use the method specified by this tag as a “provider” of data. Each row in this data provider method (as it returns an array of arrays) will be a provisioner for your test class, and for each row, your test will be executed from a clean state, so you don’t have any corruption due to bad resetting of your objects.
Hey, now we can also use this to verify if we have 3 identical symbols in a horizontal, vertical or diagonal row! Let’s work that out as well.
protected function _setDataOnGrid($data) { foreach ($data as $field) { list ($row, $column) = $field; $this->_grid->setSymbol($row, $column, self::TEST_SYMBOL); } } public function horizontalRowProvider() { return array ( array (array (array (0,0), array (0,1), array (0,2))), array (array (array (1,0), array (1,1), array (1,2))), array (array (array (2,0), array (2,1), array (2,2))), ); } /** * @dataProvider horizontalRowProvider */ public function testGridHasThreeSymbolsInARow($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inRow(self::TEST_SYMBOL)); } public function VerticalRowProvider() { return array ( array (array (array (0,0), array (1,0), array (2,0))), array (array (array (0,1), array (1,1), array (2,1))), array (array (array (0,2), array (1,2), array (2,2))), ); } /** * @dataProvider VerticalRowProvider */ public function testGridHasThreeSymbolsInAColumn($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inColumn(self::TEST_SYMBOL)); } public function DiagonalRowProvider() { return array ( array (array (array (0,0), array (1,1), array (2,2))), array (array (array (0,2), array (1,1), array (2,0))), ); } /** * @dataProvider DiagonalRowProvider */ public function testGridHasThreeSymbolsInADiagonal($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inDiagonal(self::TEST_SYMBOL)); }
Oh, Wow! We now have a complete Grid test case that isolates all Grid related tasks and responsibilities.
<?phpclass GridTest extends PHPUnit_Framework_TestCase{ const TEST_SYMBOL = 'X';
protected $_grid;
protected function setUp() { $this->_grid = new Grid(); parent::setUp(); } protected function tearDown() { parent::tearDown(); $this->_grid = null; } public function testGameGridIsSetAtStart() { $this->assertEquals(Grid::ROWS, count($this->_grid->getRows())); foreach ($this->_grid->getRows() as $row) { $this->assertInternalType('array', $row); $this->assertEquals(Grid::COLS, count($row)); foreach ($row as $column) { $this->assertNull($column); } } } public function cordinateProvider() { return array ( array (0,0), array (0,1), array (0,2), array (1,0), array (1,1), array (1,2), array (2,0), array (2,1), array (2,2), ); } /** * @dataProvider cordinateProvider */ public function testGridCanPositionASymbol($row, $column) { $this->_grid->setSymbol($row, $column, self::TEST_SYMBOL); $this->assertNotNull($this->_grid->getSymbol($row, $column)); $this->assertEquals(self::TEST_SYMBOL, $this->_grid->getSymbol($row, $column)); } protected function _setDataOnGrid($data) { foreach ($data as $field) { list ($row, $column) = $field; $this->_grid->setSymbol($row, $column, self::TEST_SYMBOL); } } public function horizontalRowProvider() { return array ( array (array (array (0,0), array (0,1), array (0,2))), array (array (array (1,0), array (1,1), array (1,2))), array (array (array (2,0), array (2,1), array (2,2))), ); } /** * @dataProvider horizontalRowProvider */ public function testGridHasThreeSymbolsInARow($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inRow(self::TEST_SYMBOL)); } public function VerticalRowProvider() { return array ( array (array (array (0,0), array (1,0), array (2,0))), array (array (array (0,1), array (1,1), array (2,1))), array (array (array (0,2), array (1,2), array (2,2))), ); } /** * @dataProvider VerticalRowProvider */ public function testGridHasThreeSymbolsInAColumn($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inColumn(self::TEST_SYMBOL)); } public function DiagonalRowProvider() { return array ( array (array (array (0,0), array (1,1), array (2,2))), array (array (array (0,2), array (1,1), array (2,0))), ); } /** * @dataProvider DiagonalRowProvider */ public function testGridHasThreeSymbolsInADiagonal($data) { $this->_setDataOnGrid($data); $this->assertTrue($this->_grid->inDiagonal(self::TEST_SYMBOL)); }}
Running this test along with our initial TictactoeTest should result in a warm, fuzzy, green feeling when you see this screen:
Next time, let’s look at our Players as the need also some respect, right.
Using a configuration file called “phpunit.xml” is by far the easiest way to run your unit tests in a consistent and organized way. It’s a simple XML configuration file and the following works for me most of the times.


Since I have tendency to write my tests before I write the code, as it allows me to think about what I expect I should get from class methods. Another benefit is that you write smaller classes with methods that just do what needs to be done.