I need to add unit testing to some old scripts, the scripts are all basically in the following form:
#!/usr/bin/perl
# Main code
foo();
bar();
# subs
sub foo {
}
sub bar {
}
If I try to 'require' this code in a unit test, the main section of the code will run, where as I want to be able to just test "foo" in isolation.
Is there any way to do this without moving foo,bar into a seperate .pm file?
Another common trick for unit testing scripts is to wrap the body of their code into a 'caller' block:
#!/usr/bin/perl
use strict;
use warnings;
unless (caller) {
# startup code
}
sub foo { ... }
When run from the command line, cron, a bash script, etc., it runs normally. However, if you load it from another Perl program, the "unless (caller) {...}" code does not run. Then in your test program, declare a namespace (since the script is probably running code in package main::) and 'do' the script.
#!/usr/bin/perl
package Tests::Script; # avoid the Test:: namespace to avoid conflicts
# with testing modules
use strict;
use warnings;
do 'some_script' or die "Cannot (do 'some_script'): $!";
# write your tests
'do' is more efficient than eval and fairly clean for this.
Another trick for testing scripts is to use Expect. This is cleaner, but is also harder to use and it won't let you override anything within the script if you need to mock anything up.
Assuming you have no security concerns, wrap it in a sub { ... } and eval it:
use File::Slurp "read_file";
eval "package Script; sub {" . read_file("script") . "}";
is(Script::foo(), "foo");
(taking care that the eval isn't in scope of any lexicals that would be closed over by the script).
Ahh, the old "how do I unit test a program" question. The simplest trick is to put this in your program before it starts doing things:
return 1 unless $0 eq __FILE__;
__FILE__
is the current source file. $0
is the name of the program being run. If they are the same, your code is being executed as a program. If they're different, it's being loaded as a library.
That's enough to let you start unit testing the subroutines inside your program.
require "some/program";
...and test...
Next step is to move all the code outside a subroutine into main
, then you can do this:
main() if $0 eq __FILE__;
and now you can test main() just like any other subroutine.
Once that's done you can start contemplating moving the program's subroutines out into their own real libraries.