nielsene wrote:How should this TDD session work? I code up the tests and implementation then post back?
I'm not sure. Maybe I could work through the problem as it appears to me - but jump in to steer the progress any time you feel like it.
It's definitely not going to be real-time

but we'll keep pressing on as time allows.
Since this is TDD, when we start out we don't have any domain objects, data access classes, or anything else that will be needed. The aim of this test case will be to discover the neighbouring objects which we think CompetitorsListHandler will need to interact with.
First test. I don't know if you need a request parameter to fetch competitor data. For the moment, I'll assume you do and so an expectation is set up for a mock Request object:
Code: Select all
class TestOfCompetitorsListHandler extends UnitTestCase
{
function TestOfCompetitorsListHandler()
{
$this->UnitTestCase();
}
function test()
{
$request =& new MockRequest($this);
$request->expectOnce('getFoo');
$handler =& new CompetitorsListHandler($request);
$this->assertIdentical($handler->try(), true);
$request->tally();
}
}
The getFoo method needs to be added to the Request class interface - other Request methods might be discovered later on. We're not trying to implement Request at the moment, just figuring out the API. This is the magic of TDD: we can come back later and write an implementation for all the classes which are discovered while writing a test case for the primary class, CompetitorsListHandler. The design ripples out in little steps from the point at which we started, evolving naturally as opposed to "big upfront design".
Code: Select all
class Request
{
function getFoo()
{
}
}
We're going to need a database object somewhere*: but will CompetitorsListHandler interact directly with it? If not we could ignore it for the moment.
If there were some domain logic to do, CompetitorsListHandler would get the data it needs from a domain object - or objects. I'll assume there isn't, and so CompetitorsListHandler will be going straight to the data access layer rather than the domain. I don't know what Database interface you're using so for the moment I'll use one I'm familiar with (we can replace that with the one you're using later):
*Actually that's not strictly true, and in a sense rather un-XP. XP emphasises coding just what you need and no more. Strictly speaking, at the moment a need for a Database object hasn't emerged. It's worth pointing out that I'm making a decision based on experience here (such as it is..). I just know I'm going to want a Database object.
Code: Select all
class Database
{
function execute($sql)
{
}
/*
return (array)
*/
function &getRow($sql)
{
}
/*
return (object) - a result iterator
*/
function &getRows($sql)
{
}
function manipulateRows($sql)
{
}
function lastInsertId()
{
}
function getError()
{
}
function disconnect()
{
}
}
You mentioned that you're using an Sql phrasebook:
Code: Select all
class CompetitorsSql
{
function findByFoo($foo)
{
}
}
Unlike the Request and Database objects, this won't be passed in. I don't expect other objects in the script will need to use the phrasebook. I'll use the standard procedure explained in the SimpleTest docs ie instantiate in a factory method which will be partially mocked in the test.
Back to the first test, adding the new mocks & some expectations.
Code: Select all
function test()
{
$foo = 'foo';
$request =& new MockRequest($this);
$request->setReturnValue('getFoo', $foo);
$request->expectOnce('getFoo');
$phrasebook =& new MockCompetitorsSql($this);
$phrasebook->expectOnce('findByFoo', array($foo));
$db =& new MockDatabase($this);
$db->expectOnce('getRows');
$handler =& new PartialCompetitorsListHandler($this);
$handler->setReturnReference('_CompetitorsSql', $phrasebook);
$handler->CompetitorsListHandler($request, $db);
$this->assertIdentical($handler->try(), true);
$request->tally();
$db->tally();
$phrasebook->tally();
}
At this point the test is failing. We've sketched out enough to make it worthwhile to start writing some code to satisfy the constraints:
Code: Select all
class CompetitorsListHandler
{
function CompetitorsListHandler(&$request, &$db)
{
$this->_request =& $request;
$this->_db =& $db;
}
function try()
{
$phrases =& $this->_CompetitorsSql();
$list =& $this->_db->getRows($phrases->findByFoo($this->_request->getFoo()));
}
function &_CompetitorsSql()
{
include('path/to/CompetitorsSql.php');
return new CompetitorsSql;
}
}
I haven't actually run this past a parser, but everything should be passing apart from $this->assertIdentical($handler->try(), true) which I've deliberately left for the moment.
Up here in the controller layer, we're primarily concerned with issuing instructions to other objects which will do all the work. Very quickly you can end up with a moderately complex web of interacting objects so it pays to think through the expectations carefully. It's easy to miss something (there are more I'll want to add later on). The main reason I wanted to assume a handler chain is to reduce the extent of this complexity. The handler chain is the application controller but we can focus on just one handler here ie one apllication controller decision and one response.
So far we've explored a way in which CompetitorsListHandler might get hold of the data it needs. The next step, in a later post, will be to explore how it's going to create a browser page - or cause a browser page to be created (jump in if you feel like it). I know you need to have several output formats but maybe we should do the http case first and take it from there.
As far as the design is concerned, methods have been identified for the Request and phrasebook objects: this is the main point of the exercise. When the TDD process is repeated, writing test cases and implementations for each mock, we might find other objects to mock and so on and so on. The design emerges in little steps, simply by writing tests. As a rule, if you come across a responsibility which doesn't fit well in the primary class, assign that to a new object and mock it out in the test. We might have "discovered" the Database object in this test although usually a work-horse class like this will be lying around, ready made.