Test Driven Development: where do I start?
Moderator: General Moderators
Test Driven Development: where do I start?
I asked a question about how to structure my classes appropriately here and there were suggestions of using TDD. I've never done this before, and other than knowing that it involves making tests, then writing code to pass those tests, then adding complexity to the tests ad (almost) infinitum, I don't know anything else. How do I make those tests? What should I test for first? And I really can't see how this will lead to well structured code. Can anyone help?
There are two unit testing frameworks for php: phpUnit and SimpleTest.
At the moment SimpleTest is the only one with mock objects (these are in the pipeline for phpUnit though). Mocks are essential for test driven design. SimpleTest also has a nice little web tester.
SimpleTest is the one I'm familiar with so, if you don't mind, that's what we'll use here. Would you like to suggest a nice simple class to start off with? I'll write a test, then you can write some code to pass the test and so on until we're done.
At the moment SimpleTest is the only one with mock objects (these are in the pipeline for phpUnit though). Mocks are essential for test driven design. SimpleTest also has a nice little web tester.
SimpleTest is the one I'm familiar with so, if you don't mind, that's what we'll use here. Would you like to suggest a nice simple class to start off with? I'll write a test, then you can write some code to pass the test and so on until we're done.
Oh, I didn't realise there was a whole topic on this thing. Wonderful.
Anyways, call me thick if you wish, I don't really know where to start. If you read the post I linked to, that explains what I'm trying to do. I suppose maybe we could start with accessing an HTML form which submits details of a gig. Is that a good place?
Anyways, call me thick if you wish, I don't really know where to start. If you read the post I linked to, that explains what I'm trying to do. I suppose maybe we could start with accessing an HTML form which submits details of a gig. Is that a good place?
Sure. We could start (almost) at the top with a class which will handle the http form submission. Let's call it "NewGig". Names are very important: suggest a better one if you like.
The first question is what is NewGig responsible for?
At this point I'm going to make some decisions which I'll come back to in a later post and explain in more detail. NewGig's responsibility will be to save the submitted data to the database then create a "submission processed successfully" view. NewGig won't be doing any input validation. It won't be dealing with form redisplay. It won't even be dealing with the "failed to update database" page. I want to keep it as simple as possible because that's the way good classes should be. It also makes the class easier to test (you'll discover that testing has a big impact on the way you code). Those other responsibilities will of course have to be carried out, but not by NewGig.
A unit test exercises the interface of a class. Various starting conditions are set up, and then assertions are made about behaviour in these different conditions. Having decided what NewGig should be responsible for, the next question is what sort of interface should NewGig have? The simplest thing I can think of is that it should have a try() method which:
(a) returns true if it succeeds in its task of updating the database and creating a "database updated" view
(b) returns nothing at all if not
Time to start testing. With SimpleTest, a test case begins like this:
I'll add a deliberately failing test:
Write this in a file somewhere 'TestOfNewGig.php'.
The assertTrue() method will pass if the argument is true and fail if not. It's "loosely" true ie "==" and not "===". Another assertion, assertIdentical() is used if you also want to assert type. You can find lists of assertions supported by SimpleTest on the lastcraft.com site. I'll also explain them here as they crop up.
You'll also need to create a file to run the test case: stick this under your web root:
SimpleTest uses an HtmlReporter if you're running tests via a browser and a TextReporter if you're running tests via the command line. The inCli() check above is there to auto-detect the context. I'd just forget about that for the moment - there's plenty other stuff to get your head around. It just works, and maybe that'll do for now.
When you point your browser at the above runner file to run the test, you'll get a red bar and a failure message if everything's working. If not, fix the include paths until you do.
Notice that the failure message tells you the line at which the failure occured and the name of the test method ("test" in this case). This instantly allows you to zero in on a problem in a real test case which might have dozens of tests and many more individual assertions.
I'm afraid I'll have to leave it there for the moment but I'm keen to follow this through. If you can get the above test running at least we'll be ready to start TDD-ing for real, tomorrow, when the make-up of NewGig will start to unfold.
The first question is what is NewGig responsible for?
At this point I'm going to make some decisions which I'll come back to in a later post and explain in more detail. NewGig's responsibility will be to save the submitted data to the database then create a "submission processed successfully" view. NewGig won't be doing any input validation. It won't be dealing with form redisplay. It won't even be dealing with the "failed to update database" page. I want to keep it as simple as possible because that's the way good classes should be. It also makes the class easier to test (you'll discover that testing has a big impact on the way you code). Those other responsibilities will of course have to be carried out, but not by NewGig.
A unit test exercises the interface of a class. Various starting conditions are set up, and then assertions are made about behaviour in these different conditions. Having decided what NewGig should be responsible for, the next question is what sort of interface should NewGig have? The simplest thing I can think of is that it should have a try() method which:
(a) returns true if it succeeds in its task of updating the database and creating a "database updated" view
(b) returns nothing at all if not
Time to start testing. With SimpleTest, a test case begins like this:
Code: Select all
// include the class file:
require_once('path/to/NewGig.php'); // edit as appropriate
// the test case:
class TestOfNewGig extends UnitTestCase
{
function TestOfNewGig() {
$this->UnitTestCase();
}
}Code: Select all
require_once('path/to/NewGig.php');
class TestOfNewGig extends UnitTestCase
{
function TestOfNewGig() {
$this->UnitTestCase();
}
function test()
{
$this->assertTrue(false);
}
}The assertTrue() method will pass if the argument is true and fail if not. It's "loosely" true ie "==" and not "===". Another assertion, assertIdentical() is used if you also want to assert type. You can find lists of assertions supported by SimpleTest on the lastcraft.com site. I'll also explain them here as they crop up.
You'll also need to create a file to run the test case: stick this under your web root:
Code: Select all
define('SIMPLE_TEST', 'path/to/simpletest/') // wherever you put the simpletest root dir (trailing slash)
require_once(SIMPLE_TEST . 'unit_tester.php');
require_once(SIMPLE_TEST . 'reporter.php');
require_once(SIMPLE_TEST . 'mock_objects.php');
require_once('path/to/TestOfNewGig.php'); // edit as appropriate
$test =& new TestOfNewGig();
if (TextReporter::inCli()) {
exit ($test->run(new TextReporter()) ? 0 : 1);
}
$test->run(new HtmlReporter());When you point your browser at the above runner file to run the test, you'll get a red bar and a failure message if everything's working. If not, fix the include paths until you do.
Notice that the failure message tells you the line at which the failure occured and the name of the test method ("test" in this case). This instantly allows you to zero in on a problem in a real test case which might have dozens of tests and many more individual assertions.
I'm afraid I'll have to leave it there for the moment but I'm keen to follow this through. If you can get the above test running at least we'll be ready to start TDD-ing for real, tomorrow, when the make-up of NewGig will start to unfold.
Last edited by McGruff on Thu Sep 08, 2005 8:50 pm, edited 1 time in total.
TDD is a way to explore some ideas. There is no "right" way to do this so feel free to suggest something different if you like.
My first idea is that user input will come from a Request object (nothing revolutionary you see these a lot). For now, all we need to know about request is that the form values are available via a bunch of named getters: getFoo(), getBar(), etc. If we've got as far as the NewGig class, we know that all required form fields have been submitted and are valid values.
The Request object will be mocked in the test. In the test case, first write a skeleton class to mock:
You need to generate the mock object:
NewGig will need to interact with a Database object - another mock. We could substitute a different db interface later if there's one you're used to, but for now I'll use this:
In the test method, the first thing to do is define the expected behaviour of the mock objects.
The mock is instantiated, "expectCallCount" states that we expect the "execute" method to be called twice, and "setReturnValue" states that it should always return a value of one.
If we do something similar for NewGigRequest:
These mock objects will have to be introduced to the primary class (NewGig). I'll come back to that in a moment. First, let's pause to look at what we've done so far. The main point is that Database and NewGigRequest don't have to exist in order to test NewGig. The skeleton classes simply define the interfaces we'd like to have, and the expectations allow us to control how these dummy objects should behave in the test (obviously you'll need to edit this somewhat: I'm sure you've got more in the form submission than just "foo" and "bar"). This allows us to write a test for NewGig which will describe how it interacts with neighbouring objects in the design which have yet to be implemented. We're free to play around with some design ideas and change them at any time.
Once the NewGig test is passing, the mock expectations & interfaces provide a blueprint for the next stage in the design where you'd write implementations for the mocks which provide the kind of behaviour defined in the NewGig test case. However, in the NewGig test case, we're not getting bogged down in the details of how these should work - we'll figure that out later.
We might not have found anything to mock in NewGig. The crucial point is to always think about responsibilities. The choice of request & database objects was made because these felt like separate responsibilities and thus should be encapsulated in new objects. The more experience you have with OOP the better you get at making these decisions (I hope to figure it out myself some day). The general point is that, whenever you discover a responsibility that doesn't fit well in the class under test, you've just discovered a new class in the design. Mock it, deferring implementation until later when you've got the test of the primary class running green.
I'm going to assume that you've read through http://www.lastcraft.com/partial_mocks_ ... tation.php. If I've made too big a jump please let me know and I'll explain in more detail.
The NewGigRequest object is going to be passed in to NewGig in the constructor. That's easy. However, the Database object will be instantiated by a factory method in NewGig:
Mock objects don't exist as real classes: partial mocks on the other hand are real classes with a few methods knocked out for testing. As explained in the SimpleTest docs, this lets you inject a mock into the test:
All the other methods will work exactly as normal.
I mentioned before that NewGig would update the db and create a view. If we assume the latter is contained in a "_print" method, we can knock that out in the partial mock of NewGig (attempting to generate a browser page would upset the test). Finally a full test:
Can you write some code which passes this test? I'm afraid you'll also need to edit the test case a little: there will be more form values tha just foo and bar, and probably more than just two queries to make. You could also substitute your own database object - and set appropriate expectations.
I hope I haven't rushed through this too quickly. I'm struggling a bit to find enough time to do this justice but I will promise not to give up until you are fully test-infected. Any questions, let fly.
My first idea is that user input will come from a Request object (nothing revolutionary you see these a lot). For now, all we need to know about request is that the form values are available via a bunch of named getters: getFoo(), getBar(), etc. If we've got as far as the NewGig class, we know that all required form fields have been submitted and are valid values.
The Request object will be mocked in the test. In the test case, first write a skeleton class to mock:
Code: Select all
class NewGigRequest
{
function getFoo()
{
}
function getBar()
{
}
}Code: Select all
mock::generate('NewGigRequest');Code: Select all
class Database
{
function execute()
{
}
}
// generate the mock object
mock::generate('Database');Code: Select all
class TestOfNewGig extends UnitTestCase
{
// etc
function test()
{
$db =& new MockDatabase($this);
$db->expectCallCount('execute', 2);
$db->setReturnValue('execute', 1);
}
}If we do something similar for NewGigRequest:
Code: Select all
class TestOfNewGig extends UnitTestCase
{
// etc
function test()
{
$db =& new MockDatabase($this);
$db->expectCallCount('execute', 2);
$db->setReturnValue('execute', 1);
$request =& new MockNewGigRequest($this);
$request->expectCallCount('getFoo', 1);
$request->expectCallCount('getBar', 1);
}
}Once the NewGig test is passing, the mock expectations & interfaces provide a blueprint for the next stage in the design where you'd write implementations for the mocks which provide the kind of behaviour defined in the NewGig test case. However, in the NewGig test case, we're not getting bogged down in the details of how these should work - we'll figure that out later.
We might not have found anything to mock in NewGig. The crucial point is to always think about responsibilities. The choice of request & database objects was made because these felt like separate responsibilities and thus should be encapsulated in new objects. The more experience you have with OOP the better you get at making these decisions (I hope to figure it out myself some day). The general point is that, whenever you discover a responsibility that doesn't fit well in the class under test, you've just discovered a new class in the design. Mock it, deferring implementation until later when you've got the test of the primary class running green.
I'm going to assume that you've read through http://www.lastcraft.com/partial_mocks_ ... tation.php. If I've made too big a jump please let me know and I'll explain in more detail.
The NewGigRequest object is going to be passed in to NewGig in the constructor. That's easy. However, the Database object will be instantiated by a factory method in NewGig:
Code: Select all
function &_Database()
{
require_once('path/to/MysqlDatabase.php');
return new MysqlDatabase(DB_HOST, DB_USER, DB_PASS);
}Code: Select all
mock::generatePartial('NewGig',
'PartialNewGig',
array('_Database'));
class TestOfNewGig extends UnitTestCase
{
// etc
function test()
{
//etc
$new_gig =& new PartialNewGig($this);
$new_gig->setReturnReference('_Database', $db);
}
}I mentioned before that NewGig would update the db and create a view. If we assume the latter is contained in a "_print" method, we can knock that out in the partial mock of NewGig (attempting to generate a browser page would upset the test). Finally a full test:
Code: Select all
mock::generatePartial('NewGig',
'PartialNewGig',
array('_Database', '_print'));
class TestOfNewGig extends UnitTestCase
{
// etc
function test()
{
$db =& new MockDatabase($this);
$db->expectCallCount('execute', 2);
$db->setReturnValue('execute', 1);
$request =& new MockNewGigRequest($this);
$request->expectCallCount('getFoo', 1);
$request->expectCallCount('getBar', 1);
$new_gig =& new PartialNewGig($this);
$new_gig->setReturnReference('_Database', $db);
$new_gig->expectOnce('_print');
$new_gig->NewGig($request);
$this->assertIdentical($new_gig->try(), true);
$new_gig->tally();
$db->tally();
$request->tally();
}
}I hope I haven't rushed through this too quickly. I'm struggling a bit to find enough time to do this justice but I will promise not to give up until you are fully test-infected. Any questions, let fly.
woah. This is better than I can get anywhere else: a tutorial specific to what I'm trying to do! Thanks a lot. It'll take me a little while to get my head around, but I'll get it eventually. Writing the stuff to pass the test should be fairly straightforward, I think. But I want to make sure I understand the test too.
Even I was in bed when you posted that. 3 AM, that's quite impressive.
Even I was in bed when you posted that. 3 AM, that's quite impressive.
- CoderGoblin
- DevNet Resident
- Posts: 1425
- Joined: Tue Mar 16, 2004 10:03 am
- Location: Aachen, Germany
In PHP5 I have an variable passed by reference fatal error so I recommend if people are starting that they use PHP4 to begin with. This error has already been previously logged as a bug with simpleTest. SimpleTest2 will be PHP5 only (no details on when at present). Class passing by reference I believe changed between PHP4 and PHP5 (even between 4.3 and 4.4 ???).
On to a more generic question on the topic in hand TDD...
http://www.lastcraft.com gives the example of having 3 directories/folders
1) classes
2) tests
3) temp
Do you tend to also use this type of structure. If so would you place all your test code in tests, mock objects in temp and the actual classes in classes or would you keep the mock objects also in tests and keep temp for temporary files if any. The way I look at it index.php(.html) would be in the top level and the core web pages which may reference the classes directory. Only developers/would have access to the tests directory-
Thanks for your time McGruff I realise how much time things like stepping us through this takes and we appreciate it.
On to a more generic question on the topic in hand TDD...
http://www.lastcraft.com gives the example of having 3 directories/folders
1) classes
2) tests
3) temp
Do you tend to also use this type of structure. If so would you place all your test code in tests, mock objects in temp and the actual classes in classes or would you keep the mock objects also in tests and keep temp for temporary files if any. The way I look at it index.php(.html) would be in the top level and the core web pages which may reference the classes directory. Only developers/would have access to the tests directory-
Thanks for your time McGruff I realise how much time things like stepping us through this takes and we appreciate it.
Here is an example of the sub-directories I have in a recent project, along with the lines of code count in each:
My tests/ directory contains files which will individually run tests on specific classes, or will run group tests to perform unit tests on all classes as well as integration and acceptance tests for the entire application.
HTH
Code: Select all
SLOC Directory SLOC-by-Language (Sorted)
2550 tests php=2542,sh=8
2315 sql sql=2315
1404 models php=1404
299 views php=299
251 actions php=251
225 top_dir php=225
0 CVS (none)
0 img (none)
0 templates (none)
0 tutorials (none)
Totals grouped by language (dominant language first):
php: 4721 (67.02%)
sql: 2315 (32.86%)
sh: 8 (0.11%)HTH