[Pdx-pm] Newbie question about testing and Perl

Eric Wilhelm scratchcomputing at gmail.com
Thu Jul 13 19:27:03 PDT 2006


# from Keith Lofstrom
# on Thursday 13 July 2006 06:42 pm:

>Hence my surprise on Wednesday night, when Eric did not turn off
>the real serial port and turn on the emulated one, and the question
>that started this thread.

Do I have an emulated one?

Let's start with something simpler:  like files.  How are you testing 
Dirvish with an emulated filesystem?

But, I think more on-point to what you're saying is that we don't have a 
really good infrastructure for automated machine-level (or macro-level) 
testing in Perl -- most of the focus has been on unit tests.  (To be 
fair, most automated test infrastructures (AFAIK) really only focus on 
unit tests.)  And really, we only need one such (cross-platform) beast, 
since all you really need to do is launch the test program in a sandbox 
and verify that the right changes to the environment happen.

The simplest and most accurate way to do this is to fork the universe 
with a copy-on-write strategy and pipe the state of the child universe 
up to the parent before killing the child (thus, smoking hardware 
and/or money in the parallel universe is ok.)  I'm not sure if the Perl 
community has the advanced physics mindshare needed for that, so we'll 
probably end up with something more approximate (and therefore 
fallible.)

As for the general case unit-testing vs macro testing, consider the 
difference in structure between these two programs:

---
# program 1
print "hello world\n";
---
# program 2
#!/usr/bin/perl

# Copyright (C) 2006 Eric L. Wilhelm

use warnings;
use strict;

=head1 NAME

hello - greets the world

=cut

package bin::hello;

use Getopt::Helpful;

sub main {
  my (@args) = @_;
  my $hopt = Getopt::Helpful->new(
    usage => 'CALLER [options]',
    '+help',
    );
  $hopt->Get_from(\@args);
  # do something with $hopt->opts or what's left of @args
  # (like maybe assign a filehandle)
  my $fh = \*STDOUT;
  print_hello($fh);
}

sub print_hello {
  my ($fh) = @_;
  print $fh "hello world\n";
}

package main;

if($0 eq ($ENV{PAR_ARGV_0} || __FILE__)) {
  bin::hello::main(@ARGV);
}

# vi:ts=2:sw=2:et:sta
my $package = 'bin::hello';
---

The latter does the same as the former (for now) but is ready to be 
gracefully expanded into a large system without turning into spaghetti 
code on day 2.

Here's what happens to the first program if you're not careful.

---
# program 1
if(@ARGV) {
  $thing_to_greet = shift(@ARGV);
}
if(@ARGV) {
  $how_to_greet = shift(@ARGV);
}
if(@ARGV) {
  $where_to_greet = shift(@ARGV);
}
if($where_to_greet) {
  open(FILE, ">$where_to_greet");
  print FILE "hello world\n";
}
if($how_to_greet) {
  print "$how_to_greet world";
  # maybe throw in a goto while you're at it, right?
}
if($thing_to_greet) {
  print "hellow $thing_to_greet\n";
}
print "hello world\n";
---

I call "not it!".  How do you test that?  I can tell you how to test my 
original "well-formed" example:

---
use Test::More 'no_plan';
my $package = require("./bin/hello");
my $prints = eval("\\&$package}::print_hello");
my $string;
open(my $fh, '>', \$string);
$prints->($fh);
like($string, qr/hello/);
---

Sure, it needs more tests.  It also needs more features.  If you wanted, 
you could even develop your little scripts in test-first mode.  I don't 
recommend that in most cases.  In fact, I don't recommend doing the 
above "my $prints = ..." junk either.  But at least you can gracefully 
test it, and if you define a package in your script, you can test the 
package.  Plus, when you're ready for modules, you just cut and paste 
and twiddle a few names, add a bit of "use <this and that>", etc.

Even as it is, we still need to test bin::hello::main() to get full 
coverage.  An alternative is to break it into smaller pieces, but  if 
you're talking to a network or etc., you'll still never get complete 
coverage until you have a (correctly) fully emulated environment.

Back to the hackjob example:  Once the code grows into a tangled mess, 
how do you refactor it and know that it does the same thing as before? 
(or at least, know that what is does differently is because you meant 
it to be that way.)  How do you check code coverage on it?

--Eric
-- 
"Beware of bugs in the above code; I have only proved it correct, not
tried it."
--Donald Knuth
---------------------------------------------------
    http://scratchcomputing.com
---------------------------------------------------


More information about the Pdx-pm-list mailing list