Test-First of OO Modules

heusserm on 2003-08-05T14:29:34

I recently did a ten-minute talk on Test-First of OO modules. This got me to thinking 'hey, I should put this on my blog' ... which got me to thinking 'hey, I should have a blog. If it were RSS enabled, Steve Hoek could use it to test his RSS Reader ...' :-)

So, here it is:



Let's say we're going to create a module called 'SomeObject'.

We knoew SomeObject will be a container, and it will take three optional paramaters on creation: The values of the entries at A, B, and C.

SomeObject will have two methods, 'Get' and 'Set'

So, we design the following tests:

1) Does SomeObject.pm compile? 2) Does SomeObject have new and get methods? 3) If I new, does it create a SomeObject object? 4,5,6) With the Object I created above, does it have the correct values for A, B, C? 7) Is the entry for D "Undefined?"

The tests are below:

#---------BEGIN CODE---------# #use lib wherever SomeObject is. ('./')? use Test::More; BEGIN { plan tests => 7 }; use_ok('SomeObject');

can_ok('SomeObject', qw(new get) );

my $t = SomeObject->new(1,2,3); isa_ok($t, 'SomeObject');

my $a = $t->get('A'); ok($a, 1, "Get 1st Parameter");

my $b = $t->get('B'); ok($b, 2, "Get 2nd Parameter");

my $c = $t->get('C'); ok($c, 3, "Get 3rd Parameter");

my $d = $t->get('D'); my $blnOk = 0; if (!defined($d)) { $blnOk=1;} ok($blnOk==1, "Making sure no extra variables are created"); # --- If we really wanted to be slick, I'd # iterate from D to ZZZ or so, but I don't # The for would have a bln we'd check, so we'd # only have one variable, not 26*26*26. # #---------END CODE---------#



When you run the test file intially, only the use should pass, because you haven't even _coded_ the other methods.

Then, you code and test. Eventually, the test results look like this:

1..7 ok 1 - use PH::SomeObject; ok 2 - PH::SomeObject->can(...) ok 3 - The object isa PH::SomeObject ok 4 - Get 1st Parameter ok 5 - Get 2nd Parameter ok 6 - Get 3rd Parameter ok 7 - Making sure no extra variables are created

Pretty nifty eh?

Not really, but it's a good start.


Nit pick

triv on 2003-08-05T15:44:30

my $d = $t->get('D');
my $blnOk = 0;
if (!defined($d)) { $blnOk=1;}
ok($blnOk==1, "Making sure no extra variables are created");
That might be better written as:
my $d = $t->get('D');
ok(!defined($d), "Making sure no extra variables are created");

Re:Nit pick

Dom2 on 2003-08-05T16:05:53

It'd be written even better as:
my $d = $t->get('D');
is( $d, 'expected_value', "get(D)");

Use of is(), like() and isa_ok() is a very good thing where possible.

-Dom

Question

phillup on 2003-08-05T18:02:59

I'm *extremely* new to testing and this post was very informative for me. Thanks.

But...

What is test # 7 about?

What if 'E' exists?

What if the object is inherited, couldn't it have a 'D'? (hmm... that wouldn't happen during testing tho... right?)

From a philosophical view, it would seem to me that tests should cover the requirements... and I didn't see anything in the requirement to drive test 7.

It also seems that if you wanted to check that there were not extra variables created, you would need to enumerate all the variables that were created... instead of picking a random variable and testing for it.

Pik Nits

schwern on 2003-08-05T21:17:25

ok($a==1, "Testing 1st Parameter");

is better written as

is( $a, 1, "Testing 1st Parameter");

for better diagnostics on failure. Any time you're writing

ok( $a == 1 )
you probably want
is( $a, 1 );
unless your name is Nick Clark, in which case you reall, really meant '==' so its cmp_ok($a, '==', 1 ) or Damian Conway, in which case there's probably a really good reason you used ok() instead of is().

Even better would be:

is( $a, 1, 'getting A' );

Mentioning that you're "testing" something in a test name is redundant and redundant, strike that out. Then there's the matter of properly describing what you're testing. You're not testing the first parameter, there's no order that I can see here, you're testing that you can get A.

Finally, assuming you don't need $a, which you don't...

is( $t->get('A'), 1, 'getting A');

why clutter up your code with unnecessary temp variables?

The last test, as mentioned before, is better written as:

ok( !defined $t->get('D'), 'no extra params created' )

to remove all that life support around it. But what if $t->get('D') did return something? How would we know what it was? Same way we do with everything else, use is().

is( $t->get('D'), undef, 'no extra params created' )