How to write unit test cases for a Perl script - perl

I'm able to write unit tests test_case.t for a Perl Module ModuleOne.pm
test_case.t
use strict;
use warnings;
use Test::More;
use Test::Cmd;
use ModuleOne; # Included the module here
my $ret = ModuleOne::methodone(args);
is($ret->{val}, 1, "Checking return value"); # success
I'm trying the achieve the same unit test cases for a perl script script_one.pl
script_one.pl
use strict;
use warnings;
use ModuleOne;
my $NAME;
my $ID;
# get parameters
GetOptions (
"name" => \$NAME,
"emp_id" => \$ID,
)
validate_params();
sub validate_params {
# this method will validate params
}
sub print_name {
# this method will print name
}
How can I include this perl file script_one.pl in test_case.t and write test cases for methods validate_params and print_name?

There are a couple of options. One is to use Test::Script to see if your code compiles and runs, and does some stuff. It's more of an integration test than a unit test though, and if you have external dependencies like writing to the file system, it's tough to mock those away like this.
Since you've got subs in the script, the easiest way is probably to require or do the script in your test file, maybe inside a different package (but that doesn't really matter). You can then call those functions, because they are in one of your namespaces.
use strict;
use warnings;
use Test::More;
package Foo {
do 'script_one.pl';
};
is Foo::print_name, 'foo', 'prints the right name';
This way you can mock dependencies more easily and you get some more control. The only thing that might be tricky is code that's not in subs and will be run at invocation, like the call to validate_params. You could just use Capture::Tiny to brush that under the rug.
The best option though is to not have functions in your script. Just make another module that has those functions, and call it in your script. It's fine to have a script like the following.
#!/usr/bin/env perl
use strict;
use warnings;
use My::Modules::Foo;
My::Modules::Foo->run; # or ::run()
It doesn't matter if it's OOP or not. The idea will be the same. If you encapsulate it properly, you can unit-test all your code without ever using that script.
Regarding the GetOpts stuff, those variables can be lexicals to the script, but your naming with the capital letters and the lack of arguments to the validate_params call indicate that they are really package-wide and are used inside the function. Don't do that. Use arguments to the subs. Put all the subs in a package, then have GetOpts in the script, and pass the options as arguments to the function.
That way you can test everything and really don't need the script.

Related

Anyway to tell if perl script is run via do?

I have a small script configuration file which is loaded from a main script.
#main.pl
package MYPACKAGE;
our $isMaster=1;
package main;
my config=do "./config.pl"
#config.pl
my $runViaDoFlag;
$runViaDoFlag=$0=~/main\.pl/; #Test if main.pl is the executing script
$runViaDoFlag=defined $MYPACKAGE::isMaster; #Test custom package variable
#Is there a 'built-in' way to do this?
die "Need to run from main script! " unless $runViaDoFlag;
{
options={
option1=>"some value",
option2=>"some value",
},
mySub=>sub {
# do interesting things here
}
}
In a more complicated config file it might not be so obvious that config.pl script is intended to only be executed by do. Hence I want to include a die with basic usage instructions.
Solutions:
test $0 for the main script name
have custom package variable defined in the main script and checked by the config script
simply have a comment in the config instructing the user how to use it.
These work, however is there some way of knowing if a script is executed via do via built-in variable/subs?
I'd offer a change in design: have that configuration in a normal module, in which you can then test whether it's been loaded by (out of) the main:: namespace or not. Then there is no need for any of that acrobatics with control variables etc.
One way to do that
use warnings;
use strict;
use feature 'say';
use FindBin qw($RealBin);
use lib $RealBin; # so to be able to 'use' from current directory
use ConfigPackage qw(load_config);
my $config = load_config();
# ...
and the ConfigPackage.pm (in the same directory)
package ConfigPackage;
use warnings;
use strict;
use feature 'say';
use Carp;
use Exporter qw(); # want our own import
our #EXPORT_OK = qw(load_config);
sub import {
#say "Loaded by: ", (caller)[0];
croak "Must only be loaded from 'main::'"
if not ( (caller)[0] eq 'main' );
# Now switch to Exporter::import to export symbols as needed
goto &Exporter::import;
}
sub load_config {
# ...
return 'Some config-related data structure';
}
1;
(Note that this use of goto is fine.)
This is just a sketch of course; adjust, develop further, and amend as needed. If this is lodaed out of a package other than main::, and so it fails, then that happens in the compile phase, since that's when import is called. I'd consider that a good thing.
If that config code need be able to run as well, as the question may indicate, then have a separate executable that loads this module and runs what need be run.
As for the question as stated, the title and the question's (apparent) quest differ a little, but both can be treated by using caller EXPR. It won't be a clean little "built-in" invocation though.
The thing about do as intended to be used is that
do './stat.pl' is largely like
eval `cat stat.pl`;
except that ...
(That stat.pl is introduced earlier in docs, merely to signify that do is invoked on a file.)
Then caller(0) will have clear hints to offer (see docs). It returns
my ($package, $filename, $line, $subroutine, $hasargs,
$wantarray, $evaltext, $is_require, $hints, $bitmask, $hinthash)
= caller($i);
In a call asked for, do './config.pl', apart from main (package) and the correct filename, the caller(0) in config.pl also returns:
(eval) for $subroutine
./config.pl for $evaltext
1 for $is_require
Altogether this gives plenty to decide whether the call was made as required.
However, I would not recommend this kind of involved analysis instead of just using a package, what is also incomparably more flexible.

Include another script in my parent Perl script

How can I include another Perl script in my base Perl script?
I have a primary source file test.pl and I want to include secondary sóurce file config.pl within it.
What is a standard method to achieve this in Perl?
(I'm guessing that the program called config.pl sets config values that you want to access in test.pl. You don't make that clear in your question.)
A simple example. If config.pl looks like this:
#!/usr/bin/perl
$some_var = 'Some value';
Then you can write test.pl to look like this:
#!/usr/bin/perl
use feature 'say';
do './config.pl';
say $some_var;
But this is a terrible idea for many reasons. Not least because it stops working when you add use strict and use warnings to either of the files (and you should aim to have use strict and use warnings in all of your Perl code).
So what's a better approach? Turn your configuration into a proper module that returns a hash (I only have a single scalar variable in my example above, but a hash gives you a way to deliver many values in a single variable). A simple approach might look like this.
A module called MyConfig.pm:
package MyConfig;
use strict;
use warnings;
use parent 'Exporter';
our #EXPORT = qw[config];
sub config {
my %config = (
some_var => 'Some value',
);
return %config;
}
1;
And a test.pl like this:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
use FindBin '$Bin';
use lib $Bin;
use MyConfig;
my %config = config();
say $config{some_var};
Having got that working, you can add improvements like parsing the %config hash from an external file (perhaps stored in JSON) and then allowing different configurations for different environments (development vs production, for example).
It's a little bit more work than your current approach, but it's far more flexible. And you can use strict and warnings.

Why does 'package' keyword in Perl modulino break tests?

Let's say there's a simple Perl script testme.pl like this:
use strict;
use warnings;
sub testme {
return 1;
}
1;
And a test file testme.t like this:
use strict;
use warnings;
use Test::More;
require_ok('testing.pl');
ok(testme());
done_testing();
Running perl testme.t seems to work as expected, but both http://www.perlmonks.org/bare/?node_id=537361 and https://stackoverflow.com/a/9134624/272387 suggest to add a package line, so I modify the original script to be:
use strict;
use warnings;
package My::Testing;
sub testme {
return 1;
}
1;
Now the test fails with:
Undefined subroutine &main::testme called at testing.t
Why so?
It fails because your testme sub is no longer in the default main namespace, it is now in the My::Testing namespace (package). You now need to access it by its full name, My::Testing::testme().
Identifiers in main don't need their package to be specified explicitly, since it's the default, but that's why the error message refers to it as main::testme.
(You could also use Exporter within the My::Testing package, export the sub, and then import it from your test script as a way of copying the sub into the test script's namespace. Or you could put another package My::Testing command into the test script, so that it will look in the My::Testing namespace by default. But referencing it as My::Testing::testme() is the simplest way to fix it and most clearly illustrates the reasoning behind the error message you're getting.)

Is it possible use or require a Perl script without executing its statements?

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?
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).
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.
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.

Is there any way to use diag() from Test::More without planning?

I'm writing some tests in Perl which have a fair amount of set up. This setup all lives in a module that the test scripts use. I want to be able to print some diagnostics from the module, and intended to use the diag function from Test::More. Problem is, when you use Test::More, it writes the plan so I get
You tried to plan twice at lib/MyTest.pm line 15.
Is there any way I can use diag (or is there an equivalent), or am I stuck with print STDERR?
For me, the following code:
#!/usr/bin/perl
use strict;
use Test::More;
diag('hello');
Just prints
# hello
Test::More won't print the plan unless you tell it to. This is done by passing args to its import:
use Test::More tests => 30;
Or by calling plan explicitly.
use Test::More;
plan(tests => 30);
use Test::More qw(no_plan)