Posted: Fri Dec 15, 2006 11:09 am
The main of testing any Setter - what goes in must come out 
A community of PHP developers offering assistance, advice, discussion, and friendship.
http://forums.devnetwork.net/
Code: Select all
$this->expectException(new DNS_Db_Exception('No valid user name has been supplied'));Code: Select all
try {
$db = new MC2_Mysql('badhost', 'baduser', 'badpass');
}
catch (Exception $e)
{
$this->assertEqual(get_class($e), 'MC2_Mysql_Exception');
$this->assertEqual($e->getMessage(), 'Invalid mysql credentials');
}Mocks simulate external classes, interfaces, and resources (see also Stubs). The idea is that each testcase tests a single class in isolation from the rest of the system. For example, if I have a class which generates SQL and executes it using ADOdb, I can't just use ADOdb directly (it would break my tests if it changed). So I mock it. Since the library is not exactly a gleaming example of OOP design (to be honest it's a nightmare to extend), I just mock an outline of it's interfaces.just on a side note... does somebody want to help explain mock objects? (I've read about them on lastcraft.com, but I could use a little further explaining)
Code: Select all
class Outline_DatabaseAbstractor {
public function Execute() {}
public function SelectLimit() {}
public function ErrorMsg() {}
public function Insert_ID() {}
}
class Outline_DatabaseAbstractorResult {
public $fields = array();
public function Close() {}
public function GetArray() {}
}Code: Select all
public function testGetByPk() {
// Mock the results class
$rs = new MockDBResult();
// Mock the connection class
$db = new MockDBAbstract();
// Tell the mocked Execute method to return a mocked results instance
$db->setReturnValue('Execute', $rs);
// Tell the results public $fields property to hold some standard data (tests are fixed)
$rs->fields = array('item_id'=>1, 'item_name'=>'Item001');
// Tell the connection class, getByPK() should only call Execute() once with
// the following SQL statement (using ADOdb auto value quoting here)
$db->expectOnce('Execute', array('SELECT * FROM test_item WHERE item_id = ?', array(1)));
// The results class should expect a call to Close()
$rs->expectOnce('Close');
// Now perform the actual test
$dao = new Quantum_Db_Access($db);
// Item is a Data Object, it proxies CRUD operations to the DAO
// Item is another of those "outline" classes we use for convenience,
// since the DAO cannot be used directly (it's proxied to).
$item = new Item(array(), $dao);
$item->getByPk(1);
// Finally check the results in the class being tested
$this->assertEqual($item->item_id, 1);
$this->assertEqual($item->item_name, 'Item001');
$this->assertTrue($item->getExists());
}Code: Select all
public function getByPk($row, $value=null)
{
$sql = 'SELECT * FROM ' . $row->getTableName() . ' WHERE ' . $row->getPrimaryKey() .' = ?';
if(isset($value))
{
$result = $this->db->Execute($sql, array( $value ));
}
else
{
$result = $this->db->Execute($sql, array( $row->getPrimaryKeyValue() ));
}
if(!$result)
{
throw new Quantum_Db_Exception($sql . '<br /><br />' . get_class($this) . ': ' . $this->db->ErrorMsg());
}
if(!empty( $result->fields ))
{
$row->import( $result->fields );
$row->setExists();
}
else
{
$row->clear(); // no data if no results
}
$result->Close();
}Code: Select all
public function __construct($host, $user, $pass, $name=null)
{
$this->connect($host, $user, $pass);
if(!is_null($name))
{
$this->selectDb($name);
}
}
public function connect($host, $user, $pass)
{
if($conn = @mysql_connect($host, $user, $pass))
{
$this->_conn = $conn;
return true;
}
throw new MC2_Mysql_Exception('Invalid mysql credentials');
}
public function isConnected()
{
return is_resource($this->_conn);
}Code: Select all
class Db
{
public function connect();
public function isConnected();
public function query();
}
class ResultSet
{
public function get();
public function next();
public function end();
}Code: Select all
$db = new Db( ... );
$rs = $db->query( ... );
while (!$rs->end())
{
echo $rs->get( ... );
$rs->next();
}Code: Select all
Mock::Generate("Db");
Stub::Generate("ResultSet", "StubResult");
class TestOfWhatever extends UnitTestCase
{
public function testSomethingHappensWhenNoResultsReturned()
{
$stub_rs = new StubResult();
$stub_rs->setReturnValue("end", true);
$mock_db = new MockDb();
$mock_db->setReturnValue("isConnected", true);
$mock_db->setReturnValue("query", $stub_rs);
$whatever = new ClassWhichUsesDb($mock_db);
$this->assertFalse($whatever->someMethodWhichNeedsResults());
}
}Code: Select all
class Db
{
public function connect();
public function isConnected();
public function query();
}
class ResultSet
{
public function get();
public function next();
public function end();
}
class TestDb extends UnitTestCase
{
public function testConnectWithGoodParams()
{
$db = new Db;
$db->connect($these, $are, $good, $params);
$this->assertTrue($db->isConnected());
}
}Well, that's the main problem in TDD: you can effectively test the interactions with entities external to host language runtime only by side-effects those interactions produce. E.g. if your db server logged connection attempts you would read&parse the log file to check if your code attempted any connects. That's the reason for you to keep your borderline classes (those that interact with 'outside word') as simple as possible. Once you created the abstraction layer you can explore your application design further, mocking external entities (actually classes which provide access to those external entities) where necessary.All I meant is how do I test the Db Class is connecting to a database