Posted: Sun Jul 31, 2005 9:27 am
Back to the test... I'll just dump Request since it isn't being used. I haven't set any expectations for the result iterator yet, so:
I'm assuming that some presentation code will make a numRows call before deciding whether to loop through the list.
So far, in the CompetitorsListHandler, we've only added some code to fetch the data needed for a browser page. This is as far as you can go before applying formatting. We're now at the edge of what I call a Cartesian boundary. These exist at inputs and outputs to the application. They don't quite correspond to the controller / view layers: an input boundary cuts across the controller layer separating input from application controller logic. An output boundary cuts across the view between the data-gathering and the output formatting. Inside these boundaries, the internal "application mind" is blithely unaware of the presentational context. Swapping out an HttpRequest object for a CLI version would allow the app to be easily changed to run on the command line, for example. The Request object is a Gateway translating http or CLI input.
Even if you don't plan on doing anything other than a web app, I think it's still useful to clearly define these boundaries. With data gathering separated from output formatting, you can re-use the same data-gathering code for any kind of output. Also, testing an UI is always a bit tricky; pushing formatting as far out to the edge of the design as possible helps by allowing you to unit-test as much of the logic as you can before being forced to pick up the web tester. At the other end, the Request object, Gateways also make useful points to mock/stub.
Once you've got the data, printing a browser page is as easy as including a template file with some presentational code in the same scope as the data.
If there were any other data to fetch in addition to the list, this would follow the same pattern of querying domain / data access objects and setting class properties. This is all pretty simple: in more complex examples maybe it could be useful to pass a container around to gather up the data.
This isn't separating out data-gathering and formatting very well but we can come back to that.
You can see we have a problem. We really don't want to output a page in the middle of the test... You could use buffering:
I'm kind of uneasy about that although it does work. It feels like cheating somehow. Note that buffering can interfere with SimpleTest reporting. If it's wrapping an assertion you might miss a fail message.
Another option is to add a print() method to CompetitorsListHandler (partially mock the class to knock it out in the test):
If you do knock out the print method in a partial mock, none of the presentational code in the template is being exercised so the expectations on the mock result iterator would have to be changed.
This has a better separation of formatting and data-gathering. You could make print() an abstract method and subclass with different print strategies.
Code: Select all
function test()
{
$phrasebook =& new MockCompetitorsSql($this);
$phrasebook->expectOnce('findAll');
$row_0 = array('quark'=>'foo');
$row_1 = array('strangeness'=>'bar');
$row_2 = array('charm'=>'buff');
$it =& new MockResultIterator($this);
$it->setReturnValue('next', false);
$it->setReturnValueAt(0, 'next', $row_0);
$it->setReturnValueAt(0, 'next', $row_1);
$it->setReturnValueAt(0, 'next', $row_2);
$it->expectCallCount('next', 4);
$it->expectCallCount('numRows', 1);
$db =& new MockDatabase($this);
$db->setReturnReference('getRows', $it);
$db->expectOnce('getRows');
$handler =& new PartialCompetitorsListHandler($this);
$handler->setReturnReference('_CompetitorsSql', $phrasebook);
$handler->CompetitorsListHandler($request, $db);
$this->assertIdentical($handler->try(), true);
$db->tally();
$phrasebook->tally();
$it->tally();
}So far, in the CompetitorsListHandler, we've only added some code to fetch the data needed for a browser page. This is as far as you can go before applying formatting. We're now at the edge of what I call a Cartesian boundary. These exist at inputs and outputs to the application. They don't quite correspond to the controller / view layers: an input boundary cuts across the controller layer separating input from application controller logic. An output boundary cuts across the view between the data-gathering and the output formatting. Inside these boundaries, the internal "application mind" is blithely unaware of the presentational context. Swapping out an HttpRequest object for a CLI version would allow the app to be easily changed to run on the command line, for example. The Request object is a Gateway translating http or CLI input.
Even if you don't plan on doing anything other than a web app, I think it's still useful to clearly define these boundaries. With data gathering separated from output formatting, you can re-use the same data-gathering code for any kind of output. Also, testing an UI is always a bit tricky; pushing formatting as far out to the edge of the design as possible helps by allowing you to unit-test as much of the logic as you can before being forced to pick up the web tester. At the other end, the Request object, Gateways also make useful points to mock/stub.
Once you've got the data, printing a browser page is as easy as including a template file with some presentational code in the same scope as the data.
Code: Select all
function try()
{
$phrases =& $this->_CompetitorsSql();
$list =& $this->_db->getRows($phrases->findAll());
if(false !== $list) {
include($this->_template);
return true;
}
}This isn't separating out data-gathering and formatting very well but we can come back to that.
You can see we have a problem. We really don't want to output a page in the middle of the test... You could use buffering:
Code: Select all
function test()
{
// ...
// ...
// ...
ob_start();
$return_value = $handler->try();
ob_end_clean();
$this->assertIdentical($return_value, true);
// ...
// ...
// ...
}Another option is to add a print() method to CompetitorsListHandler (partially mock the class to knock it out in the test):
Code: Select all
function try()
{
$phrases =& $this->_CompetitorsSql();
$this->_list =& $this->_db->getRows($phrases->findAll());
if(false !== $this->_list) {
$this->print();
return true;
}
}
function print()
{
include($this->_template);
}This has a better separation of formatting and data-gathering. You could make print() an abstract method and subclass with different print strategies.