6

The docs I find around the ’net and the book I have, Perl Testing, either say or suggest that unit-testing for Perl is usually done when creating modules.

Is this true? Is there no way to unit-test actual programs using Test::More and cousins?

tchrist
  • 78,834
  • 30
  • 123
  • 180
Lazloman
  • 1,289
  • 5
  • 25
  • 50
  • What exactly are the units within your script that you want to test? Would they be better moved into a module? – Wooble Feb 03 '12 at 19:44
  • 1
    Yes, **of course** you want to test programs! You want to test them just as much as you would any other piece of software. You can test different inputs, different configurations of CLI switches, different envariables. You can run fuzz-testing. There are millions of things to test. Not testing programs is a big mistake. – tchrist Feb 03 '12 at 20:21
  • "One program's `main` is another program's module." Modularization can help testing but certainly any program you want to maintain needs a test suite. Whether `Test::More` is the best tool for the job depends on the job. Personally, I have been creating shell scripts to test my command-line tools; if you make them output their results in TAP format, you can easily mix them with Perl test scripts, and use `prove` to run the whole test suite. – tripleee Feb 04 '12 at 14:28

2 Answers2

11

Of course you can test scripts using Test::More. It's just harder, because most scripts would need to be run as a separate process from which you capture the output, and then test it against expected output.

This is why modulinos (see chapter 17 in: brian d foy, Mastering Perl, second edition, O'Reilly, 2014) were developed. A modulino is a script that can also be used as a module. This makes it easier to test, as you can load the modulino into your test script and then test its functions like you would a regular module.

The key feature of a modulino is this:

#! /usr/bin/perl
package App::MyName; # put it in a package

run() unless caller; # Run program unless loaded as a module

sub run {
  ... # your program here
}

The function doesn't have to be called run; you could use main if you're a C programmer. You'd also normally have additional subroutines that run calls as needed.

Then your test scripts can use require "path/to/script" to load your modulino and exercise its functions. Since many scripts involve writing output, and it's often easier to print as you go instead of doing print sub_that_returns_big_string(), you may find Test::Output useful.

Community
  • 1
  • 1
cjm
  • 61,471
  • 9
  • 126
  • 175
  • The dismissive way that some people treat programs makes no sense to me. – tchrist Feb 03 '12 at 20:22
  • 3
    I'd add that passing `@ARGV` into `run()` makes it even easier to test. Then the tests don't have to awkwardly pass arguments into `run()` with `local @ARGV`. It's also worth noting that the modulino technique allows you to test not just `run` but any other subroutine in your program. – Schwern Feb 03 '12 at 20:59
  • Thanks, I'm going to give this a go. – Lazloman Feb 03 '12 at 21:03
  • @Schwern I confess I’m not familiar with this modolino technique, but I certainly have been known to make executable modules that run a simple `main` if not required. – tchrist Feb 03 '12 at 21:15
0

It's not an easiest way to test your code, but you can test the script directly. You can run the script with specific parameters using system (or better IPC::Run3), capture output and compare it with expected result.

But this will be a top level test. It'll be hard to tell which part of your code caused problem. Unit-tests are used to test individual modules. This makes it easier to see where the problem came from. Also testing functions individually is much easier, because you only need to think about what can happen in smaller piece of code. The way of testing is depend on your project size. You can, of cause, put everything into single file, but putting your code into module (or even splitting it into different modules) will give you benefits in future: code can be easier reused and tested.

Axeman
  • 29,660
  • 2
  • 47
  • 102
Ivan Nevostruev
  • 28,143
  • 8
  • 66
  • 82