in reply to The hills are alive...
So, lets say we wanted to modify this code a little. First of course we would write unit tests so we have some confidence we don't break anything along the way. The following code could go in an external test script that uses SoundofMusicSong, but for this example just replace the 1; line at the end of the module with:
sub note_to_sound { my ($note) = @_; return $notes{$note}; } use Test::More tests => 8; return 1 if caller(); # Done if loaded as a module my $song = make_SoM_song (join ' ', 'a' .. 'g'); my @expected = map{note_to_sound($_)} 'a' .. 'g'; Test::More::is_deeply($song, \@expected, "Notes map correctly to phras +es"); my %noteCounts; my $samples = 100000; my $nominal = $samples / 7; ++$noteCounts{random_SoM_note($_)} for 1 .. $samples; Test::More::ok (abs($noteCounts{$_} - $nominal) < 200, "Random distrib +ution of $_: $noteCounts{$_}") for map {note_to_sound($_)} 'a' .. 'g';
note_to_sound was added to the module to facilitate testing - it avoids having to access %notes directly. Now lets rework the code a little:
package SoundOfMusicSong; use strict; use warnings; my %SoM = ( 'c' => {phrase => 'a deer a female deer', sound => 'do'}, 'd' => {phrase => 'a drop of golden sun', sound => 're'}, 'e' => {phrase => 'a name I call myself', sound => 'me'}, 'f' => {phrase => 'a long long way to run', sound => 'fa'}, 'g' => {phrase => 'a needle pulling thread', sound => 'so'}, 'a' => {phrase => 'a note to follow so', sound => 'la'}, 'b' => {phrase => 'a drink with jam and bread', sound => 'te'}, ); my @notes = keys %SoM; sub make_SoM_song { my ($user_song) = @_; return [ map {exists $SoM{$_} ? $SoM{$_}->{sound} : 'not a note'} split /\W+/, $user_song ]; } sub getPhrase { my ($user_song) = @_; my $notes = make_SoM_song($user_song); my @new_song = map {$SoM{$_} ? "$_ $SoM{$_}" : 'not a note'} @$not +es; return \@new_song; } sub random_SoM_note { return $SoM{$notes[rand @notes]}{sound}; } sub random_SoM_song { my ($number_of_notes) = @_; $number_of_notes ||= int(rand(100)) + 1; return [map {random_SoM_note()} 1 .. $number_of_notes]; } sub note_to_sound { my ($note) = @_; return $SoM{$note}{sound}; }
Now we have, for better or worse, a major overhaul of the module, but when we run the unit tests they still all pass so the reworked code is good to go.
The test suite doesn't test the entire interface provided by the module so the test suite is incomplete and maybe we've introduced badness in the untested areas? If we are really serious about unit testing we would run code coverage tools (see Devel::Cover) in conjunction with the unit tests to ensure all the code we care about is tested.
|
|---|