Sunday, August 28, 2011

Quality Assurance on PHP projects - PHPUnit part 3


Time for the third part on unit testing with phpunit in my Quality Assurance on PHP projects series. In part one we started writing unit tests for a simple game of tic-tac-toe. In part two we started converting our unit tests into actual code and moved our general unit test code for grids into a Grid focussed unit test. In this part, we're looking at how we can optimize the tests for our players.

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.
<?php
class 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.
InvalidArgumentException
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.
InvalidArgumentException
So we can increase the quality of the game by making sure only allowed symbols are used.

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.
<?php
class 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'.


Tuesday, August 23, 2011

Quality Assurance on PHP projects - PHPUnit part 2

     I hope everyone enjoyed my first article on unit testing with phpunit where I started writing a few tests that would guide us building our little game of tictactoe. Today I'm going start with turning these tests into working code and adjusting our tests to have a clear separation of responsibility. Since we already know what the code should produce, we only have to work out the details.

Our tests tell us we have four classes:
  • Tictactoe: the main class that is responsible for the game and how it should be played
  • Grid: is the class that's responsible for setting up the playing grid
  • Players: a collection class containing both player objects
  • Player: the class defining a single player
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?
  • Grid is part of our main TicTacToe class
  • has a method that fetches rows of type array
    • and each row has columns as an array
We can also make the following assumptions:
  • Grid class will instantiate a grid with 3 rows and 3 columns
  • When calling "getRows()" we retrieve an array of 3 columns
  • Each field in the grid will have a null value as a default
  • A Player must have a way to set his "symbol" on a specific position on the grid
  • Must have a way to verify if a given symbol exists in either a horizontal, a vertical or a diagonal row
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:
  • Grid is part of our main TicTacToe class (is not really relevant for our Grid functionality)
  • has a method that fetches rows of type array
    • and each row has columns as an array
  • Grid class will instantiate a grid with 3 rows and 3 columns
  • When calling "getRows()" we retrieve an array of 3 rows with each 3 columns
  • Each field in the grid will have a null value as a default
  • A Player must have a way to set his "symbol" on a specific position on the grid
  • Must have a way to verify if a given symbol exists in either a horizontal, a vertical or a diagonal row
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.
<?php
class 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.
<?php
class 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.

Wednesday, August 17, 2011

Quality Assurance on PHP projects - PHPUnit part 1

Of all tools available for improving quality assurance, there's one tool that is the core tool you have to master: PHPUnit. PHPUnit is a complete testing framework crafted by Sebastian Bergmann (@s_bergmann), who ported existing xUnit frameworks to PHP. And with this testing framework you're able to test your functionality in an automated way before you push code into production.

[editorial] As I cannot summarize the whole usage of phpunit in one blogpost, this will be a sequence of several articles that will pick up a specific task you want to cover with phpunit.

Installation
Well, first of all you need to have PHPUnit installed on your system. The easiest way to accomplish this is to use the PEAR installer.

user@server: $ pear channel-discover pear.phpunit.de
user@server: $ pear channel-discover components.ez.no
user@server: $ pear channel-discover pear.symfony-project.com

Once you've got the right channels, you can install the framework:

user@server: $ pear install phpunit/PHPUnit

Configuration
You can use three methods to configure the way PHPUnit executes tests on your project:
  • a configuration file
  • command line parameters
  • a combination of both
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.

NOTE: I use phpunit with a configuration file that matches my project settings, so you might need to modify these settings if you're using a framework or have a different source code layout.

<phpunit bootstrap="./TestHelper.php" colors="true">
    <testsuite name="Unit test suite">
        <directory>./</directory>
    </testsuite>

    <filter>
        <whitelist>
            <directory suffix=".php">../src/</directory>
        </whitelist>
    </filter>
</phpunit>

This simple configuration file has 3 major parts:

1. The phpunit configuration tag
This tag starts the complete configuration, but also includes a bootstrap file (for bootstrapping your application like Zend Framework's bootstrapper class) and enables colors to show green, yellow and red colored bars to indicate the status of your tests. Since I'm a fan of a warm, fuzzy feeling whenever I see green, I enable this by default.

2. Testsuite section
The testsuite is a wrapper that bundles all your tests into a single suite, so you can call it immediately at execution. You can provide it with a name and a directory.
This section is very useful if you have multiple sections in your projects that you want to separate, as you can define the test suites based on their paths on the filesystem.

3. Filtering
Filtering allows you to quickly define what needs to be tested and what needs to be excluded. In my case I need just to investigate my Zend Framework application path and my custom library path. I also want to exclude all my templates as they don't contain any logic.

You can add also a section for logging where you can define code coverage reports (requires XDebug), a testdox progress report and other kinds of reports provided by PHPUnit. But since I will transfer these tasks to another tool, I'm not going to discuss it here.

I also use a bootstrap file for phpunit called TestHelper.php that allows me to "bootstrap" my test suite. This bootstrap file sets my defined constants, include paths, timezone and error reporting levels.

This is a very minimalistic bootstrapper:
<?php
// file: tests/TestHelper.php

// set our app paths and environments
define('BASE_PATH', realpath(dirname(__FILE__) . '/../'));
define('APPLICATION_PATH', BASE_PATH . '/src');
define('TEST_PATH', BASE_PATH . '/tests');
define('APPLICATION_ENV', 'testing');

// Include path
set_include_path(
    '.'
    . PATH_SEPARATOR . BASE_PATH . '/src'
    . PATH_SEPARATOR . get_include_path()
);

// Set the default timezone !!!
date_default_timezone_set('Europe/Brussels');

// We wanna catch all errors en strict warnings
error_reporting(E_ALL|E_STRICT);

So how do you start?
Testing is really simple but for most people the complexity of having code testing other code and just report back if it's working or not is a bit confusing for people that haven't written any tests before.

For the most part, in my professional career I get a lot of excuses like "no time", "no budget" or "no interest". If it's truly such a big problem to cope with something as simple as writing a test, a career change might be in order. And I mean it. I believe that if you're serious about your job, you have to take a little responsibility in it. And writing tests is not that hard.

Let's begin with a classic example: a tic-tac-toe game. This is a simple pen-and-paper game for two players (O and X) that uses a grid of 3 by 3. The purpose of the game is to have a row, a column or a diagonal row filled with only O's or X's. More information can be found at http://en.wikipedia.org/wiki/Tic-tac-toe.

source: http://en.wikipedia.org/wiki/File:Tic-tac-toe-game-1.png

So how do you start with this kind of a game? Very simple: define the objects, the rules and the goal of this game in a list that makes sense to you. I like to define it as follows:
  • objects:
    • symbol X for user 1
    • symbol Y for user 2
    • play grid of 3 rows by 3 columns
  • rules:
    • write your symbol once for each turn within the grid
  • goal:
    • prevent opponent to win
    • win by having 3 symbols on a single row, a single column or a single diagonal row
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.

TictactoeTest
Let's define our general outline for our main class Tictactoe. We already know a few things: two players, a grid of 3 rows by 3 columns and a winner when three identical symbols are in a single row horizontal, vertical or diagonal.

In test code it would look something like this:
<?php
// file: tests/TictactoeTest.php
require_once 'Tictactoe.php';
class TictactoeTest extends PHPUnit_Framework_TestCase
{
    public function testGameGridIsSetAtStart()
    {
        // test to see a grid is created of 3 by 3 and all fields are null
    }
    public function testGamePlayersAreSetAtStart()
    {
        // test to see players are set up at start with symbols X and O
    }
    public function testGameCanBePlayed()
    {
        // play a simple game with two players turn by turn
        // returning true or false would notify us if there's a winner or not
    }
}

We test 3 major things here: that a grid is set, that two players are created and that the game can be played. We don't worry for edge cases or exceptions now, as we can take care of that later.

When we run this test, we get a bunch of errors, basically saying that we don't have a Tictactoe class file yet. At this point, this is very normal.

user@server:/dev/ttt/tests $ phpunit
PHP Warning:  require_once(Tictactoe.php): failed to open stream: No such file or directory in /dev/ttt/tests/TictactoeTest.php on line 3

Warning: require_once(Tictactoe.php): failed to open stream: No such file or directory in /dev/ttt/tests/TictactoeTest.php on line 3
PHP Fatal error:  require_once(): Failed opening required 'Tictactoe.php' (include_path='.:/dev/ttt/src:.:/usr/lib/php/pear') in /dev/ttt/tests/TictactoeTest.php on line 3

Fatal error: require_once(): Failed opening required 'Tictactoe.php' (include_path='.:/dev/ttt/src:.:/usr/lib/php/pear') in /dev/ttt/tests/TictactoeTest.php on line 3

We've got are main goals set, now it's time to make some assertions to see we get the information we want.

First of all, we're going to setUp and tearDown our test case, so we know each test starts with a clean slate.

class TictactoeTest extends PHPUnit_Framework_TestCase
{
    protected $_ttt;

    protected function setUp()
    {
        parent::setUp();
        $this->_ttt = new Tictactoe();
    }
    protected function tearDown()
    {
        $this->_ttt = null;
        parent::tearDown();
    }

Now we need to fill in the blanks regarding the test methods. Let's start with our first method: 

    public function testGameGridIsSetAtStart()
    {
        // we should be able to retrieve the grid from our Tictactoe class
        $grid = $this->_ttt->getGrid();

        // let's make sure we created a grid class to handle all grid related manipulations
        $this->assertInstanceOf('Grid', $grid);

        // we know the grid is 3 x 3, so let's count the rows
        $this->assertEquals(3, count($grid->getRows()));

        // and for each row, let's count the columns
        // and since we're at it, let's check that their fields are empty
        foreach ($grid->getRows() as $row) {
            $this->assertInternalType('array', $row);
            $this->assertEquals(3, count($row));
            foreach ($row as $column) {
                $this->assertNull($column);
            }
        }
    }

So, what do we learn from this test? We need to have a Grid class to take care of all grid related things and we need to ensure each field is empty before we start playing. Take a note of this as we need it later!

The next step is to set up our players. We know we have two players and each player has his own symbol (X or O).

    public function testGamePlayersAreSetAtStart()
    {
        // we should be able to retrieve all players from our Tictactoe class
        $players = $this->_ttt->getPlayers();

        // ensure that players have their own class to take care of all players
        $this->assertInstanceOf('Players', $players);

        // we know we only have two players
        $this->assertEquals(2, count($players));

        // we want to ensure that each player has the correct symbol
        $this->assertEquals(Player::PLAYER_X, $players->seek(0)->current()->getSymbol());
        $this->assertEquals(Player::PLAYER_O, $players->seek(1)->current()->getSymbol());
    }

This test teaches us we need another two classes for our players. One class is acting as a collection implementing the SeekableIterator and Countable interfaces from SPL (I just love these, so I know I will use them!). The second class defines the player and the symbol it gets.

Our last test method is all about playing the game.

    public function testGameCanBePlayed()
    {
        // let's pick just one player
        $player = $this->_ttt->getPlayers()->seek(0)->current();

        // and see if we can win if we created a row
        $this->assertFalse($this->_ttt->play(0, 0, $player));
        $this->assertFalse($this->_ttt->play(0, 1, $player));
        $this->assertTrue($this->_ttt->play(0, 2, $player));


        $this->_ttt->setGrid(new Grid());

        // and see if we can win if we created a column
        $this->assertFalse($this->_ttt->play(0, 1, $player));
        $this->assertFalse($this->_ttt->play(1, 1, $player));
        $this->assertTrue($this->_ttt->play(2, 1, $player));


        $this->_ttt->setGrid(new Grid());

        // and see if we can win if we go diagonal left to right
        $this->assertFalse($this->_ttt->play(0, 0, $player));
        $this->assertFalse($this->_ttt->play(1, 1, $player));
        $this->assertTrue($this->_ttt->play(2, 2, $player));


        $this->_ttt->setGrid(new Grid());


        // and see if we can win if we go diagonal right to left
        $this->assertFalse($this->_ttt->play(0, 0, $player));
        $this->assertFalse($this->_ttt->play(1, 1, $player));
        $this->assertTrue($this->_ttt->play(2, 2, $player));
    }

In my next article we're going to set up our classes that should turn this test into a success. In the mean time, I challenge you to figure out how the code might look like. And if you want, share that code with everyone by posting it on pastebin.com, pastie.org or using github's gist.


Sunday, August 07, 2011

Quality Assurance on PHP projects - PHPDocumentor feedback

First of all, thank you all for the enormous feedback I got on my latest article on documentation of code. I got a lot of comments on the usage of PHPDocumentor for the following reasons:
  • the project seems to be no longer maintained
  • it's not ready for php 5.3
  • it uses too much resources when using it on big projects
I have to agree that these reasons are valid enough to step away from PHPDocumentor as a tool for documentation purposes and look for a better alternative. So I've investigated one tool most people have commented on or tweet-ed/facebook-ed/g+-ed on: DocBlox.

Before I start a flamewar, I'm all for investigating more tools as time permits me, but these are my first impressions on the tool that had the most buzz in my personal social zone. If your tool of preference is not listed here, write an article on it and put the link here in the comments (or ping back to this article in your own post).

DocBlox seems to be the youngest project of all the various projects suggested to me, but gets a lot of support from major players in the field of PHP and is supported by my favorite build tool Phing (a tool I'll describe in detail in another article).

Installation
You can simply use PEAR to install DocBlox.

user@server $: pear channel-discover pear.docblox-project.org
user@server $: pear channel-discover pear.michelf.com

Select a package you need, in this case docblox/DocBlox-0.12.1 as the project is still in beta (when writing this article) and version DocBlox-0.12.2 has some issues

user@server $: pear install -a docblox/DocBlox-0.12.1

Usage
DocBlox is pretty simple in it's usage and works indeed better than PHPDocumentor, although I had to contact the project lead Mike van Riel (@mvriel) regarding a dependency error on Zend Framework, but it should be fixed now or any time soon.

My dear friend Matthew Weier O'Phinney (@weierophinney) had taken up my challenge to write an article about the usage of DocBlox and I have to say, it's a very good introduction that allowed me to use it immediately.

As a result, I was able to test-run both PHPDocumentor and DocBlox using Phing resulting in a small win for DocBlox. I haven't made a true benchmark test including memory consumption, cpu usage but I think that someone with more sysadmin background knowledge can easily set it up to give a more in-depth detail on which tool performs the best.



Creative Commons License
This work is licensed under a Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 License.