The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

Test::ShellScript - Shell script testing module

SYNOPSIS

  use Test::ShellScript;
  
  run_ok("myScript.sh")
  variable_ok("executed", "false");
  variable_ok("output", "0");
  variable_ok("executed", "true");

DESCRIPTION

Call me insane, but sometimes a shell script becames too important or complicated that no one wants to touch it. Then why not to add some testing to gain some confidence and avoid disrupting its funcitionality ?

INTRO

The idea behind this module is to make testing simple for shell script writers and that means :

 * simple testing code addition on shell script
 * minimal knowledge about Perl

This module will parse the output for the program under test, extracting and parsing only those devoted to testing. Each line is identified with a header, followed by a text that must be in the next format :

    variable=value

simply the same format that a properties file line, assigning no meaning to any value or variable name.

Suppose you have the next script that executes a command passed in the command line

    #!/bin/bash

    ## execute any command
    [[ -z $1 ]] && $*

Unfortunately you don't know why it doesn't work, then you add some testing code

    #!/bin/bash

    echo "TEST: executed=false"
    
    ## execute any command
    [[ -z $1 ]] && echo "TEST: executed=true" && $*

Run it again and you'll see the output

    TEST: executed=false

Too bad, no execution simply because [[ -z $1 ]] is a bad test for a command passed in the command line it must be [[ ! -z $1 ]], then the new output for our script is

    TEST: executed=false
    TEST: executed=true

Using Test::ShellScript

Testing such a simple script is a piece of cake, but when scripts grow in size and complexity the game to play is very different. Anyway let me introduce you to how to use Test::ShellScript with the previous simple script.

Start creating a text file with the next lines and name it run.t :

  #!/usr/bin/perl

  use 5.006;
  use strict;
  use warnings;
  use Test::ShellScript;

This simply instructs to use Perl (version 5.006 and onwards must be used), tell Perl to be srtict (that means declare all your variables), warn you about suspicious stuff and finally instruct it to use the shell script testing module.

Now you must instruct it to run you program and then begin to look for the different variables and its associated values to show up in the right order. Add the next lines to run.t

    run_ok( '/path/to/run/command ls', "^TEST:");
    isCurrentVariable("executed");
    isCurrentValue("false");
    nextSlot();
    isCurrentVariable("executed");
    isCurrentValue("true");

run_ok runs your program and parses the output. It expects two parameters, the first one is the whole command line to execute and the second is a regular expression that is used to identify the lines used for testing. In this case the lines begin (^) with the word TEST followed by the character ':'

Once parsed the whole output devoted to testing is our timeline and each output line becomes a time slot. Once run_ok is executed we're at the first one, then if we ask for the value of 'executed' it must be false or, in other words, the current variable should be 'executed' and its value 'false'.

Once we tested for these values nothing else can be done except for moving to the next time slot, then continue testing up to the end.

Great, we have created our first test and if we execute it the output will be something like this

    ok 1 - command: '/path/to/run/command ls'
    ok 2 - Current variable is 'executed'
    ok 3 - Current value is 'false'
    ok 4 - Current variable is 'executed'
    ok 5 - Current value is 'true'
    1..5

meaning that each test was passed (ok) followed by a test number and a human readable comment. At the end the tests numbers executed is show. This is fine if you have 5 or so tests to execute but what if you need to make a hundred tests on tenths of scripts ? No way Macaya !

There's a wonderful module called Test::Harness that parses this output and shows us tests statistics on success and failure. To run our humble test just type the command 'perl -MTest::Harness -e 'runtests(@ARGV);' t/run.t' and lets the magic begin :

 # perl -MTest::Harness -e 'runtests(@ARGV);' t/run.t
 t/run.t .. ok    
 All tests successful.
 Files=1, Tests=5,  0 wallclock secs ( 0.03 usr  0.00 sys +  0.02 cusr  0.01 csys =  0.06 CPU)
 Result: PASS

Continuos mode

What we've been using in the previous section is called Step-by-step mode, where you can do testing one slot at a time.

Now suppose you need to look for a variable content then you'll need to do it inside a loop, search for it and once it's found look at it's value. Just to save errors, time, resources and everything-else-you-want-to the continuous mode will help you.

Let's continue with the previous example. Add the next lines to run.t

    reset_timeline();
    variable_ocurrences("executed",2);
    reset_timeline();
    variable_ocurrences("non_exisent_variable",0);

What they do is to reset the timeline, count how many times the variable 'executed' is shown, resets the timeline again and now counts how many times the 'non_exisent_variable' variable is shown.

using Test::More

As a side effect a compatbility with Test::More has been added meaning that you can mix Test::More and Test::ShellScript testing in the same test.

e.g.

  use 5.006;
  use strict;
  use warnings;
  use Test::ShellScript;
  use Test::More;
  
  my $testNUmber = 1;
  run_ok( '/path/to/run/command ls', "^TEST:");
  
  ### --- step by step mode
  $testNUmber++;
  isCurrentVariable("executed");
  
  ### using Test::More
  ok( $testNUmber == 2 );

  ### Back again to Test::ShellScript !!!
  isCurrentValue("ls");
  isCurrentValue("false");
  

METHODS

Step-by-step mode

run_ok

  run_ok("myScript.sh argument1 argument2", "TEST");

Runs the command passed in the first argument and parses the command output. Accept as testing lines for output the ones with the regexp passed as second argument.

isCurrentVariable

  isCurrentVariable("Variable_name");

test if the variable passed as argument exists in the current time slot

isCurrentValue

  isCurrentValue("Variable_value");

test if the value for the variable in the current time slot is the one passed as parameter

nextSlot

  nextSlot()

advances to the next time slot

resetTimeline

  resetTimeline()

moves to the first time slot

Continuos mode

variable_ok

  variable_ok("VARIALE_NAME", "value");

Looks for the variable value and compares it to the passed value

variable_ok

  variable_ocurrences("VARIALE_NAME", right_number_of_ocurrences);

Counts how many times the variable is shown, and compares it with the passed value

reset_timeline

  reset_timeline()

moves to the first time slot

CONTRIBUTORS

Matias Palomec <matias.palomec at gmail.com>
Luis Agustin Nieto

AUTHOR

Copyright (C) 2010 by Victor A. Rodriguez. El bit Fantasma (Bit-Man) http://www.bit-man.com.ar/