Lazy test development

Making the Perl debugger do the work

Joe McMahon

http://ibiblio.org/mcmahon

Bugs happen

Test-driven development

The cycle

Lazy Testing

Here be dragons

Here be dragons

Here be dragons

It's not that bad, really

Documentation to the rescue

Finding an easier way

.perldb

afterinit()

So what do we want to do?

What about testing this?

Test::More

prove

flatbox:~ joe$ prove t/*.t
t/001_load..............ok                                                   
t/002_is_a_test.........ok                                                   
t/003_is_a_sub..........ok                                                   
t/004_get_test_names....ok                                                   
t/005_tdump.............ok                                                   
All tests successful.
Files=5, Tests=71,  1 wallclock secs ( 0.36 cusr +  0.13 csys =  0.49 CPU)
flatbox:~ joe$ 

Getting loaded

Getting lazier

Making the debugger type things

Integrated testing

The dawn of recorded history

hdump first cut

Getting it in

  DB<2> DB::hdump "debugger.history"
Recording history for this session in debugger.history ... done (3 lines).

  DB<3>

hdump demo

flatbox:~ joe$ perl -demo 

Loading DB routines from perl5db.pl version 1.27
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   mo
auto(-1)  DB<1> use Test::More qw(no_plan)

  DB<2> is(1,1,"constant")
ok 1 - constant

  DB<3> isnt(2,1,"constant too")
ok 2 - constant too

  DB<4> like("I like coconut cream pie", qr/I like .* pie/,"likes pie");
ok 3 - likes pie

  DB<5> DB::hdump sample
Recording history for this session in sample ... done (6 lines).

  DB<6> q
1..3
flatbox:~ joe$

hdump output

?
use Test::More qw(no_plan)
is(1,1,"constant")
isnt(2,1,"constant too")
like("I like coconut cream pie", qr/I like .* pie/,"likes pie");
DB::hdump sample

Where'd the question mark come from?

This is a recording

Playback

Kick it up a notch

Inspecting Test::More - part 1

# Returns true if this is a sub in Test::More, false otherwise
sub is_a_sub {
  local $_ = shift;
  (!/^_/) and eval "defined &Test::More::$_";
}

Inspecting Test::More - part 3

# Build a hash to check names against.
%test_names = map { $_ => 1 } get_test_names()
      unless keys %test_names;

Are you a test?

# Returns true if this line of history is a Test::More test.
sub is_a_test {
  my $line = shift;
  if (($possible, $paren) = /^\s*(\w+)\(/) {
    return $test_names{$possible};
  }
}

The test dumper - tdump

sub tdump($) {
    my $outfile = shift;
    %test_names = map { $_ => 1 } get_test_names()
      unless keys %test_names;
    print OUT "Recording tests for this session in $outfile ...";
    unless (open TFH, ">$outfile") {
       print OUT " can't write history: $!\n";
    }
    else {
       my @tests = map { "$_;\n" }
                   grep { is_a_test($_, \%test_names) } @DB::truehist;
       print TFH "use Test::More tests=>", int @tests, ";\n";
       print TFH join "\n", @tests, "\n";
       close TFH;
       print OUT " done (", int @tests, " tests).\n";
    }
  }

tdump in action

Flatbox:~ joe$ perl -demo

Loading DB routines from perl5db.pl version 1.27
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   mo
auto(-1)  DB<1> use Test::More qw(no_plan)

  DB<2> is(1,1,"constants are constant")
ok 1 - constants are constant

  DB<3> isnt(2,1, "still constant")
ok 2 - still constant

  DB<4> use_ok("CGI")
ok 3 - use CGI;

  DB<5> tdump "sample.t"
Can't locate object method "tdump" via package "sample" 
(perhaps you forgot to load "sample"?) at 
(eval 13)[/System/Library/Perl/5.8.5/perl5db.pl:620] line 2.

  DB<6> DB::tdump sample.t
Recording tests for this session in samplet ... done (3 tests).

  DB<7> q
1..3

The resulting test

Flatbox:~ joe$ cat samplet
use Test::More tests=>3;
is(1,1,"constants are constant");
isnt(2,1, "still constant");
use_ok("CGI");
Flatbox: ~ joe $  prove -v samplet 
sample....1..3
ok 1 - constants are constant
ok 2 - still constant
ok 3 - use CGI;
ok
All tests successful.
Files=1, Tests=3,  1 wallclock secs ( 0.16 cusr +  0.05 csys =  0.21 CPU)
Flatbox:~ joe$ 

Not bad, but let's do better

Comment hack

Leverage the hack

tdump version 2

sub tdump($) {
    ...
    my @tests = map { "$_;\n" } 
                grep { is_a_test($_, \%test_names) } @DB::truehist;
    print TFH "use Test::More tests=>", int @tests, ";\n";

    my @lines = @DB::truehist;
    while (@lines) {
      my $line = shift @lines;
      my $forced_capture = 0;
      while ($lines[0] =~ /^\s*#/) {
        print TFH $lines[0],"\n";
        $forced_capture = 1;
        shift @lines;
      }
      next unless $forced_capture or is_a_test($line, \%test_names); 
      $line = "$line;\n" unless $line =~ /;$/;;
      print TFH $line;
    }
    ...

Sample run

flatbox:~ joe$ perl -demo

Loading DB routines from perl5db.pl version 1.27
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   mo
auto(-1)  DB<1> use Test::More qw(no_plan)

  DB<2> is(1,1)
ok 1

  DB<3> $x=2  

  DB<4> # This sets up the next test

  DB<5> isnt($x,1)
ok 2

  DB<6> DB::tdump "comments.t"
Recording tests for this session in comments.t ... done (2 tests).

  DB<7> q
1..2
flatbox:~ joe$

Sample output

flatbox:~ joe$ cat comments.t 
use Test::More tests=>2;
is(1,1);
# This sets up the next test
$x=2;
isnt($x,1);
flatbox:~ joe$ 

Getting seriously lazy

watchfunction

Bug city

Even lazier version

flatbox:~ joe$ perl -demo

Loading DB routines from perl5db.pl version 1.27
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   mo
auto(-1)  DB<1> use Test::More qw(no_plan)

  DB<2> is(1,1)
ok 1

  DB<3> tdump "demo.t"
Recording tests for this session in demo.t ... done (1 tests).

  DB<4> q
1..1
flatbox:~ joe$ cat demo.t
use Test::More tests=>1;
is(1,1);
flatbox:~ joe$

Default save

The whole ball of wax

flatbox:~ joe$ perl -demo

Loading DB routines from perl5db.pl version 1.27
Editor support available.

Enter h or `h h' for help, or `man perldebug' for more help.

main::(-e:1):   mo
auto(-1)  DB<1> use Test::More qw(no_plan)

  DB<2> is(1,1)
ok 1

  DB<3> use_ok("CGI")
ok 2 - use CGI;

  DB<4> $foo = new CGI;

  DB<5> # Create a CGI object and see if type is right

  DB<6> isa_ok($foo, "CGI")
ok 3 - The object isa CGI

  DB<7> tdump
Recording tests for this session in test.t ... done (3 tests).

  DB<8> q
1..3
flatbox:~ joe$

Test with setup and comments

flatbox:~ joe$ cat demo.t
use Test::More tests=>3;
is(1,1);
use_ok("CGI");
# Create a CGI object and see if type is right
$foo = new CGI;
isa_ok($foo, "CGI");
flatbox:~ joe$

What else?

Like to play with it?

Thank you