Bonus: testing forces you to modularize your code
Computers are tireless
Bonus: a test is an example of intended use
Code coverage: how much of the software is tested?
Realism: do the tests represent actual use?
Priority: are you testing the important code?
Reward: are you testing the hardest code?
Compute the day of the month for a given MadMongers meeting
First implementation: use Time::Local
to compute the day of week given a date.
(then test, then change implementation and re-test)
lib/Calendar/MadMongers.pm
1: package Calendar::MadMongers; 2: 3: use warnings; 4: use strict; 5: use Readonly; 6: use Time::Local qw(timelocal); 7: 8: # Hard-code 3rd Tuesday 9: # Sun=0, Mon=1, Tue=2, ... 10: Readonly my $DAY_OF_WEEK => 2; 11: Readonly my $WEEK_OF_MONTH => 3; 12:
13: sub calc_date { 14: my ($pkg, $year, $month) = @_; 15: my @date = (0, 0, 19, 1, $month-1, $year-1900); 16: # s m hh d month year 17: @date = localtime(timelocal(@date)); 18: while ($date[6] != $DAY_OF_WEEK) { 19: $date[3]++; 20: @date = localtime(timelocal(@date)); 21: } 22: for (my $i = $WEEK_OF_MONTH; $i > 1; $i--) { 23: $date[3] += 7; 24: } 25: return $date[3]; 26: }
ok($boolean, $comment);
is($got, $expected, $comment);
isnt($got, $expected, $comment);
like($got, qr/regex/, $comment);
unlike($got, qr/regex/, $comment);
is_deeply($got, [1,2,{foo=>'bar'}], $comment);
t/calendar.t
1: #!/usr/bin/perl -w 2: 3: use strict; 4: use Calendar::MadMongers; 5: use Test::More tests => 4; 6: 7: my $pkg = 'Calendar::MadMongers'; 8: is($pkg->calc_date(2008, 4), 15, 'April 2008'); 9: is($pkg->calc_date(2008, 5), 20, 'May 2008'); 10: is($pkg->calc_date(2008, 6), 17, 'June 2008'); 11: is($pkg->calc_date(2008, 7), 15, 'July 2008');
perl -Ilib t/calendar.t
1..4 ok 1 - April 2008 ok 2 - May 2008 ok 3 - June 2008 ok 4 - July 2008
Optimized for concise, linear test programs. Common usage:
.t
filest/calendar-wrong.t
1: #!/usr/bin/perl -w 2: 3: use strict; 4: use Calendar::MadMongers; 5: use Test::More tests => 4; 6: 7: my $pkg = 'Calendar::MadMongers'; 8: is($pkg->calc_date(2008, 4), 16, 'April 2008'); 9: is($pkg->calc_date(2008, 5), 20, 'May 2008'); 10: is($pkg->calc_date(2008, 6), 17, 'June 2008'); 11: is($pkg->calc_date(2008, 7), 15, 'July 2008');
perl -Ilib t/calendar-wrong.t
1..4 not ok 1 - April 2008 # Failed test 'April 2008' # at testing-example/t/calendar-wrong.t line 8. # got: '15' # expected: '16' ok 2 - May 2008 ok 3 - June 2008 ok 4 - July 2008 # Looks like you failed 1 test of 4.
If you have your code set up the right way, Test::Harness makes running tests easier.
lib
, e.g. lib/Foo/Bar.pm
t
, e.g. t/bar.t
prove -l
" or "prove -lv
"prove -l t/calendar.t
t/calendar......ok All tests successful. Files=1, Tests=4, 0 wallclock secs (0.01 usr 0.01 sys) Result: PASS
prove -lv t/calendar.t
t/calendar...... 1..4 ok 1 - April 2008 ok 2 - May 2008 ok 3 - June 2008 ok 4 - July 2008 ok All tests successful. Files=1, Tests=4, 0 wallclock secs (0.02 usr 0.01 sys) Result: PASS
prove -l t/calendar-wrong.t
t/calendar-wrong......1/4 # Failed test 'April 2008' # at t/calendar-wrong.t line 8. # got: '15' # expected: '16' # Looks like you failed 1 test of 4. t/calendar-wrong...... Dubious, test returned 1 Failed 1/4 subtests Test Summary Report ------------------- t/calendar-wrong.t (Wstat: 256 Tests: 4 Failed: 1) Failed test: 1 Non-zero exit status: 1 Files=1, Tests=4, 0 wallclock secs (0.01 usr 0.01 sys) Result: FAIL
13: use DateTime; 14: 15: sub calc_date { 16: my ($pkg, $year, $month) = @_; 17: my $date = DateTime->new(year => $year, 18: month => $month); 19: while ($date->day_of_week != $DAY_OF_WEEK) { 20: $date->add(days => 1); 21: } 22: for (my $i = $WEEK_OF_MONTH; $i > 1; $i--) { 23: $date->add(days => 7); 24: } 25: return $date->day_of_month; 26: }
perl -Ilib t/calendar.t
1..4 ok 1 - April 2008 ok 2 - May 2008 ok 3 - June 2008 ok 4 - July 2008
perl -MDevel::Cover -I lib t/calendar.t
... -------------------------- ------ ----- ----- ------ ---- File stmt bran cond sub pod -------------------------- ------ ----- ----- ------ ---- lib/Calendar/MadMongers.pm 100.0 n/a n/a 100.0 0.0 t/calendar.t 100.0 n/a n/a 100.0 n/a Total 100.0 n/a n/a 100.0 0.0 -------------------------- ------ ----- ----- ------ ----
cover -report html
Writing HTML output to cover_db/coverage.html ... done.
...
Java, C++ and other languages like "xUnit" testing, popularized by JUnit
lib/Calendar/MadMongers/Object.pm
1: package Calendar::MadMongers::Object; 2: use Carp; 3: 4: sub new { 5: my ($class) = @_; 6: return bless { events => [] }, $class; 7: } 8: 9: sub all { 10: my ($self) = @_; 11: return @{$self->{events}}; 12: } 13:
lib/Calendar/MadMongers/Object.pm
14: sub add { 15: my ($self, $what, $when) = @_; 16: if (!$when || !$when->isa('DateTime')) { 17: croak 'Expect a DateTime instance'; 18: } 19: push @{$self->{events}}, 20: { description => $what, when => $when }; 21: return; 22: } 23:
lib/Calendar/MadMongers/Object.pm
24: sub select { 25: my ($self, $year, $month) = @_; 26: if ($month < 1 || $month > 12) { 27: croak 'Invalid month'; 28: } 29: return grep {$_->{when}->year == $year && 30: $_->{when}->month == $month} 31: $self->all; 32: } 33: 34: 1;
t/classes.t
1: #!/usr/bin/perl -w 2: use strict; 3: 4: # equivalent to '-I t/lib' 5: use File::Basename; 6: use File::Spec; 7: use lib File::Spec->catdir( 8: dirname(__FILE__), 'lib'); 9: 10: use Calendar::MadMongers::TestObject; 11: Calendar::MadMongers::TestObject->runtests;
t/lib/Calendar/MadMongers/TestObject.pm
1: package Calendar::MadMongers::TestObject; 2: 3: use base 'Test::Class'; 4: use Test::More; 5: use Test::Exception; 6: 7: use Calendar::MadMongers::Object; 8: use DateTime; 9: 10: sub every_method : Test(setup) { 11: shift->{self} 12: = Calendar::MadMongers::Object->new; 13: }
t/lib/Calendar/MadMongers/TestObject.pm
15: sub empty : Test(1) { 16: my $self = shift->{self}; 17: is(scalar $self->all, 0, 'empty at start'); 18: } 19: 20: sub add : Test(1) { 21: my $self = shift->{self}; 22: my $now = DateTime->now(); 23: $self->add('a test', $now); 24: is_deeply([$self->all], 25: [{description => 'a test', 26: when => $now}], 'add'); 27: }
t/lib/Calendar/MadMongers/TestObject.pm
29: sub add_error : Test(1) { 30: my $self = shift->{self}; 31: dies_ok { $self->add(DateTime->now(), 32: 'a test'); }; 33: } 34: 35: sub select : Test(2) { 36: my $self = shift->{self}; 37: $self->add('a', DateTime->new(year => 2008, 38: month => 4)); 39: is(+ $self->select(2008, 4), 1, 'April'); 40: is(+ $self->select(2008, 5), 0, 'May'); 41: }
perl -Ilib t/classes.t
1..5 ok 1 - add ok 2 - add error ok 3 - empty at start ok 4 - April ok 5 - May