in reply to TDD: a test drive and a confession

Ah, yes, one of my favorite subjects :)

The single greatest benefit that I have gained from tests is that I am a better programmer. It's not just that I can run my tests and know that my code works; by writing my tests first, I am forced to look at things from the standpoint of interfaces and who will be using my code. I need things to be simple and intuitive. As a result, my code is more likely to be simple and intuitive. Quite frequently, after a heavy refactoring of legacy code, I find myself with a module that has a bunch of one line methods that rarely take more than one argument (other than $self). That makes me feel good.

And I always find strange bugs that I never thought about, but I also find myself writing fewer bugs since I am focusing on the simplest thing that can get the tests to pass.

The non-deterministic factor is what worries me. That's actually quite a debate at my current job. Currently, we create test data, put it in a database and then nod when we get the test data back out. I think that's a problem because we're testing our expections or what the data should look like rather than testing what the data actually is. The rebuttal is "we can't test it if we don't know what it returns". In other words, they're afraid of non-deterministic tests.

There are plenty of ways to get around this. We can make raw SQL queries against a copy of the live data to verify that the code returns the correct data. We can use regular expressions to verify that the form of the data that we return is correct, or subroutines that return true or false depending upon whether or not the data is in the correct domain (e.g, day of week is 1 to 7 instead of an integer).

Finally, you can use Test::MockObject to override the difficult interfaces. If you find this is too much overhead, you can also override interfaces directly in your code. Here's one handy method. The subroutine you test calls fetchrow_arrayref internally:

my $results; my $test_data = [qw{foo bar baz}]; { no strict 'refs'; local $^W; # suppres redefined warnings my $arrayref_called = 0; local *{'DBI::fetchrow_arrayref'} = sub {$arraref_called++; $test_ +data }; $results = some_func($some_arg); is($arrayref_called, 1, '... and fetchrow_arrayref should be calle +d only once'); } ok($results, '... and some_func succeeded'); is_deeply($results, $test_data, '... and it should return the correct +data');

Finally, for troublesome built-ins that can be overridden, try ex::override.

my @rand = qw/ 0.78195951257749 0.625570958105044 0.884315045123127 0.137177303290578 0.0650888668725038 /; use ex::override rand => sub {unshift @rand => pop @rand; $rand[0]}; print rand, $/ for 1 .. 5;

Now you have deterministic "random" numbers :)

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Re: TDD: a test drive and a confession
by gwadej (Chaplain) on Sep 18, 2003 at 03:36 UTC

    As an alternative for the rand example, you could use the other function in the rand API.

    srand( 17 ); print rand, $/ for 1 .. 5;

    Since, by definition, all random numbers produced by rand are deterministic, this simplifies your test.

    Of course, this approach falls down if the implementation of rand is different on different platforms. (I don't know if Perl has one internally or just bases the code on the rand provided by the C compiler.)

    G. Wade
Re: Re: TDD: a test drive and a confession
by demerphq (Chancellor) on Sep 18, 2003 at 22:42 UTC

    Update: God, I'm such a dolt sometimes. I had originally read this thread yesterday before the other reply on this subject was there. I didn't refresh and just thought I'd point out the below in a kind of teasing way. It has no real bearing on your underlying point I realize but I thought id take a nudge at the last comment. Anyway, this subject has been covered already. Move along now, try not to laugh at the merphqling...

    Now you have deterministic "random" numbers :)

    E:\>perl -le "srand 1; print join q(,),map { int rand 100 } 1..10;" 0,56,19,80,58,47,35,89,82,74 E:\>perl -le "srand 1; print join q(,),map { int rand 100 } 1..10;" 0,56,19,80,58,47,35,89,82,74 E:\>perl -le "srand 1; print join q(,),map { int rand 100 } 1..10;" 0,56,19,80,58,47,35,89,82,74 E:\>perl -le "srand 1; print join q(,),map { int rand 100 } 1..10;" 0,56,19,80,58,47,35,89,82,74

    Digital computers don't make non-deterministic "random" numbers... :-)


    ---
    demerphq

    <Elian> And I do take a kind of perverse pleasure in having an OO assembly language...