Alright, walk me through TDD one more time...
Moderator: General Moderators
- Maugrim_The_Reaper
- DevNet Master
- Posts: 2704
- Joined: Tue Nov 02, 2004 5:43 am
- Location: Ireland
This method doesn't exist...
do I need to write it? (I had to change the Mock::generate to static for it to work
I thought simpletest was php5, but I guess not)
EDIT: Nevermind, this did the trick: 
Code: Select all
$this->expectException(new DNS_Db_Exception('No valid user name has been supplied'));EDIT: Nevermind, this did the trick:
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');
}- Maugrim_The_Reaper
- DevNet Master
- Posts: 2704
- Joined: Tue Nov 02, 2004 5:43 am
- Location: Ireland
expectException() exists in the current released version - 1.0.1beta, and of course in CVS. The new beta was released not long ago (and it needs an update for PHP5.2) so I would check your version. Good adjustment for its absence though
.
Mock::generate() is static - in older versions of SimpleTest it sometimes breaks in PHP5.1.4+ (another reason to update). If you haven't guessed, you should update SimpleTest for each new PHP release. SimpleTest is moving towards PHP5, but the current track is staying on PHP4 for a while longer.
Once it's Mocked, I then setup the mock with some standard and fixed behaviour. For example, I want the class being tested to use ADOdb correctly. To check this, I need to test that certain ADOdb methods are called, with the correct parameters, and that the return values are correctly handled. This is another reason to Mock - mocks can be told what methods, parameters to expect, and will fail if a test if these expectations are not met. You can't do that with the real library!
Here's a test which check the getByPK() method of a DAO. The DAO generates the SQL, uses ADOdb to execute the SQL, and handles the return results. The outlines above are mocked to simulate ADOdb (it uses separate Connection and Result classes).
Note, the actual implementing code extract....
Mock::generate() is static - in older versions of SimpleTest it sometimes breaks in PHP5.1.4+ (another reason to update). If you haven't guessed, you should update SimpleTest for each new PHP release. SimpleTest is moving towards PHP5, but the current track is staying on PHP4 for a while longer.
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() {}
}Here's a test which check the getByPK() method of a DAO. The DAO generates the SQL, uses ADOdb to execute the SQL, and handles the return results. The outlines above are mocked to simulate ADOdb (it uses separate Connection and Result classes).
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();
}so how would you "mock" a database connection... say I have some methods like this...
How could I mock this connection? How could I test something like this without having to set up a real database?
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);
}- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
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());
}
}OK, thank you, but that's beyond even what I was talking about (but helpful none-the-less for my next step). All I meant is how do I test the Db Class is connecting to a database (I know you can't w/out a database, so I wanted to know how you would mock one)...
Is there even any point to testing that? It's either true or an exception is thrown... not a whole lot to test... 
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());
}
}- Chris Corbyn
- Breakbeat Nuttzer
- Posts: 13098
- Joined: Wed Mar 24, 2004 7:57 am
- Location: Melbourne, Australia
Don't test that the Db can connect to a Db. That has nothing to with your design, it's just a detail to do with the server/configuration. Just assume the Db does connect. You could factor connect() into a method which returns a boolean and then simply get your mock to return boolean false if you wanted to simulate a connection failing. The implementation of how it returns false is up to you, although if (@mysql_connect( ... )) seems pretty solid 
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