Funny Experience...

Discussion of testing theory and practice, including methodologies (such as TDD, BDD, DDD, Agile, XP) and software - anything to do with testing goes here. (Formerly "The Testing Side of Development")

Moderator: General Moderators

User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Funny Experience...

Post by nielsene »

Just finished my first feature driven by TDD. I feel I cheated a little bit when I got the the final display portion -- didn't really use TDD to drive the inclusion of my normal layout/cut-n-paste of legacy code. However its still 9 test cases, 29 passing asserts for this one feature. Already experienced a few of the niceties of design evolving in hopefully good ways.

So I quickly started on a second feature, closed related, two asserts later, feature complete. I'm about to go onto the next feature. Then i realize, I hadn't checked the local install to actually see the features in action...... Thought it was very interesting, that sub-consciously I didn't feel the need to verify by eye and just "knew" it should be working. So now that I realized that I had to go check, and sure enough it works.

Testing backend code, database helper classess, output formatting, and navigation/form handling, so its really an end-to-end TDD feature threaded through the system. The only gap is that I'm not testing the query against a live DB yet.

Thanks for being patient with me McGruff! Having someone to share their experiences, is keeping me focused on switching development methodology.
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

Glad to help :) Testing gives you a lot of confidence. It's very hard to work with untested code once you're test-infected. It feels like you're working blindfold. I just don't feel like I'm in control.

As well as knowing when you're done with new code, tests are a huge help when you're refactoring. State-based integration tests provide a constant reference point against which to refactor classes south in the design.

And the holy grail is "instant" deployment with scripted checkout from version control followed by a test run (and rollback if something fails) while you catch up on your emails or whatever...
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Well the euphoria wore off tonight :(

Went to start on the next related feature, existing TDD-derived architecture won't work. Its probably a good thing, pushing me into a Domain Model... Was hoping to avoid that until I finished the current iteration, but....

Going to sleep on it, then start hacking away tomorrow. Well leave the existing two features in place and work on the third, upon finishing looking at the refactoring --which will likely require widescale changes in the unit tests...

I'm feeling a little confused. This is the second time I've led myself dowwn a dead-end that's going to require complete rewrite of the functionality. Upon identifying a solution, I can't see anyway to salvage/migrate the existing tests cases, so I start from scratch -- new tests, new production code until I've fully reimplemented the previous code, then can rip out/abandone the old test code/production code. I don't see right now, how the unit tests will every allow refactoring without widescale breakages...
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

Ouch. Is it really that bad?

The only general advice I can think of is to keep methods tight and short. The less they do, the more likely it is they can be moved easily into a new class.

It's sort of assumed in XP that there will be refactoring stages to consolidate the first working solution - at least that's how I understand it. Interaction-based tests (using mocks) will break but state-based integration tests (which don't really care what classes are employed south in the design) should provide a constant backbone against which to refactor. It depends just how much of the design you need to tear up.

I think a lot of the work you've been doing recently has been in figuring out some testing strategies in addition to working through design ideas. With that out of the way, hopefully the next attempt will be a lot easier.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Well, attempting to rest up and look at it with fresh eyes failed... My brain hates to stop coding at night...

But I found one possible solution; I'm just not sure I like it.... But then again TDD isn't about liking the solution, just getting it to work.

The situation was:
Still working on this blastedly simple feature: generate five different lists, allow (currently) two different formats for each.

I'd gotten TDD derived implementations for the first two list with both formats and the web interface.

The implementation was that paticular ListFormmater classes could correctly format a ResultSet from the DB. A PhraseBook provided the query via a named label.

The ListFormatter hasn't actually been written, only two subclasses that should be refactored soon into a proper tree, pushing some material up.

Now the next two list formats are actually nested lists -- lists of lists. In the current non-web admin-only cli script version its done using an N+1 lookup pattern (1 query for the list of sublists needed, then one for the each sublist contents.) I breiefly toyed with combining them into a single query (lots of redundant data in each row then). The list formatters would need to do a lot more "state keeping" as some the list headers require knowledge of the contents of the list. But the design was just feeling very bad and alrarming..... Now it doesn't sound quite so bad... I might try it.

The first work around I thought of was moving to a Domain Model and then passing one of the objects from that to a print formatter of some kind instead of the result set. At least that way the nested nature would be easily modelled. Its where the design probably should go long term, but its more work than I can spare before the next fieldable increment is needed.

While try to fall asleep, I decided I could stick with the original N+1 queries. In this case I think its more efficient than usually. The N queries are only returning a single column, but a lot of row ( >> N). The initial query returns 4 rather wide columns, but N is typically small, ~30-60. Adding the 4 wide columns onto each row of the small narrow query would drastically bloat the data transfer, plus complicate the data processing -- intead of the nested easy to export, I'd have the added check for if the mainly redudnant columns had changed, and if so finalize the header, export the list, and start a new sublst, etc.

However, nothing says I can't let the ListFormatter subclass issue queries. This will push the DB connection and the PhraseBook into a Registry which would be good and I can then slowly start removing all the deeply chained db references throughout the system in the legacy code. After generating the new ResultSet, the master ListFormatter would call a different ListFormmatter to handle it. Its not perfect. It works and I think it would ease the eventual transition to a Domain Model (each seperate ListFormatter could be moved to the appropriate class's toString style method)
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

How about trying a TDD session?

I'll start near the top, although not quite at the top because we'd need to spend a fair amount of time working through Page/Application Controllers.

A "CompetitorsListHandler" will be responsible for responding to a request for a list of competitors. This would be one of several possible responses to the request, each with its own handler class. Others might be a 400 "BadRequestSyntax" handler, various other error states, authentication, authorisation, etc. More or less one for each possible browser page which might be issued in response to the request. They're loaded in a ChainOfResponsibility, slightly amended from the GoF CoR. The chained handlers don't themselves call the next in the chain (a lazy-loading "chain manager" class does that), but apart from that it's the same logical switch/case behaviour ie only one handler will ever be active.

The different handlers as a rule don't need to know anything about each other (if they weren't loosely-coupled a CoR might not be such a good idea). That helps a great deal to narrow the focus: we can TDD away at the CompetitorsListHandler and simply ignore everything else. By the time a request gets to a CompetitorsListHandler, user input has been validated, a successful database connection has been made, and the user has been authenticated/authorised (if that was required).

If you can buy all that (we should change tack if it doesn't strike a chord) the handler has a very simple interface expected by the chain manager (I'll post the latter if you like):

Code: Select all

class CompetitorsListHandler
{
    /*
        param (object) - a Request object is automatically passed to all handlers
    */
    function CompetitorsListHandler(&$request) 
    {
        $this->_request =& $request;
    }
    /*
        return (true/null) - true if the object can handle the request, nothing if not
    */
    function try() 
    {
    }
}
All the class does is try to fetch some data and serve up a page. If it succeeds it returns true.

Shall we continue?
Last edited by McGruff on Fri Aug 05, 2005 9:10 pm, edited 3 times in total.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Its not the way I would have begun, but I'll follow/code along. It'll be educational in any case.

How should this TDD session work? I code up the tests and implementation then post back?

(I have to head out for a bit now, should be back around 2300 GMT. If the TDD Session require more "real time" type interaction, it'll probably have to wait until be both have matching freee time again)
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

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.
Last edited by McGruff on Fri Aug 05, 2005 9:10 pm, edited 3 times in total.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

I'm not quite seeing it yet; but I think that's because I'm more of a stubber than a mocker. I normally try to start at the domain objects and grow out, so I always have something concrete to work off of, but I'm seeing a little how that suffers from not having a "big picture" view that the top-down method that mocking tends to use.

I think I also have a few "viewpoint" issues. (This isn't the way I've done things before, so of course it looks odd, type of stuff).

This may be an odd question, but what drove the choice to include the db as a parameter to the Handler, instead of either as a component of the request or an instance fetchable from a Registry?

Other than that the only real change in the implementation code I'd want to make would be in the _CompetitorSql function:

Code: Select all

function &_CompetitorSql() {
      return new PhraseBook('/path/to/competitorSql.sql');
    }
to align with my current usage. Typically to retrieve a phrase you'd give it a named label of the phrase to the generic getPhrase method. (Which eventually will also take a second array argument to replace placeorder vvariables in the phrase, but I haven't need it yet so its not implements.) Your DB class versus mine is close enough to make no real difference.

This list doesn't have any parameters to fetch out of the Request object, so the $this->_request->getFoo() wouldn't be needed in this exact case, but its easier for me to pare down what I don't need, so lets keep it in there to see how this develops if there had been a need for a query parameter.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

McGruff wrote: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.
For now I'm only considering two output-formats, both on the html page, but within a <pre> block for cut-pasting by the user. The email'd attachment method can easily wait.

I'm trying to think through what i would do next in your example.
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Hmm. Feels like to be that we'd need to extend the Handler interface to add an "execute" method?

Ie the Controller chains through the registered handlers, looking for one that anseers TRUE to try(). Then the Controller would need to tell it to execute() which sould probably return the web page to displaay (and any side-effects such as sending the email).

The controller then sends the page, and exits.

Or am I misunderstanding the Handler pattern/doing too much BDUF?

If I'm viewing it right, then we're close to done with the simple test of try(), might need a little bit of error testing for corner cases, but otherwiee....

However if that's the case, I don't see why we're bothering to fetch any data in the try? (Unless its to check that the request foo exists?)
User avatar
Burrito
Spockulator
Posts: 4715
Joined: Wed Feb 04, 2004 8:15 pm
Location: Eden, Utah

Post by Burrito »

I just set my newest goal in life:

to understand what the hell you two talk about in this forum....
User avatar
nielsene
DevNet Resident
Posts: 1834
Joined: Fri Aug 16, 2002 8:57 am
Location: Watertown, MA

Post by nielsene »

Burrito wrote:I just set my newest goal in life:

to understand what the hell you two talk about in this forum....
:)

Read the page linked in McGruff's sig (the SimpleTest home page) or check out either of the two books mentioned in the book review thread I started.
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

The Registry could well be a good idea and I'd probably be using one in real life. Strictly speaking a need for a Registry hasn't emerged yet so, in best XP don't-do-anything-you-don't-have-to style, I decided to keep it as simple as possible.

The try() method is the execute() method, really. That gives handler objects the command to get started doing whatever it is they have to do. In this case, get the data and serve a page. Return true if that is successful. It could be as simple as:

Code: Select all

function try() 
    {
        $phrases =& $this->_CompetitorsSql();
        $list =& $this->_db->getRows($phrases->findByFoo($this->_request->getFoo()));
        if(false !== $list) {
            include($this->_template);
            return true;
        }
    }
Now we run into one of those boundary issues. Really you'd want to use the web tester to check the browser page. I'm going to have to quit for the day here but will be back tomorrow - or rather later today.
Last edited by McGruff on Fri Aug 05, 2005 9:08 pm, edited 3 times in total.
McGruff
DevNet Master
Posts: 2893
Joined: Thu Jan 30, 2003 8:26 pm
Location: Glasgow, Scotland

Post by McGruff »

Burrito wrote:I just set my newest goal in life:

to understand what the hell you two talk about in this forum....
It really is much simpler than it looks. In a test case, you instantiate classes then make assertions about their behaviour (you can of course test procedural code as well). This would always pass, unless php was badly broken:

Code: Select all

$this->assertTrue(is_string('foo'));
Substitute the return value of a class method or a function for 'foo' and that's basically all there is to it.

Things get a little more complicated with mock objects. These don't really exist as real classes. The SimpleTest framework will create them on the fly and we can set up return values, expected call counts, and expected parameters for each mock method call to make them behave in the way that we'd want the real object to behave. That allows you to write a test for the primary class before you've written any neighbouring objects with which it will interact.
Last edited by McGruff on Fri Aug 05, 2005 9:05 pm, edited 3 times in total.
Post Reply