http://www.perlmonks.org?node_id=497613

Eimi Metamorphoumai has asked for the wisdom of the Perl Monks concerning the following question:

I've recently seen a lot of references to test driven development on the Monastery, and it sounds like a great idea. So I keep wondering whether I can use it. The problem is that as a sysadmin, almost all the code I write is essentially glorfied shell scripts. How can testing be incorporated into code that doesn't really compute values, but instead does something?

For instance, I've got a script that moves users' mailboxes into their home directories (where quota is more strictly monitored) if they get too large. I've got another that kills processes left by users that are no longer logged in (where "logged in" involves checking whether the user has any of various shells still running) and deletes any temporary files they left behind.

The details aren't that important, but what matters is that instead of being a collection of small subroutines that compute values (and can therefore be tested with all sorts of inputs to confirm that they produce the correct output), what I deal with are just big (but usually no more than a screenful or so of text) scripts that interact with the real world. I haven't come up with any automated testing proceedures, but putting one out there untested is very dangerous (when a couple thousand users lose their mail due to a bug, people become unhappy). So I'm doing ad hoc testing by running with the "payload" commented out, and multiple prints all over the place to show what it would be doing, and knowing in my gut that there has to be a better way. So I come seeking wisdom.

What are good approaches you've come up with for this sort of thing? Is there a way to use the Test:: modules with programs like this, or is there another approach that works better?

Replies are listed 'Best First'.
Re: Test driven development and glue code
by InfiniteSilence (Curate) on Oct 05, 2005 at 14:50 UTC
    Looking at your examples:

  • I've got a script that moves users' mailboxes into their home directories...: Write functions that return the list of users and test that; write functions that move fake files to temp directories, etc.
  • I've got another that kills processes left by users ..: Create fake processes and then kill them. Check to ensure that you killed them. Simple.

    I'm not a test-driven coding zealot though. In some cases the script is so simple it really doesn't warrant much testing. The argument for always using test-driven development is a lot like 'thou shalt use classes to code everything...so sayeth the OO God' which, as you may already know, is a bunch of nonsense. Use whatever you think is best.

    Celebrate Intellectual Diversity

Re: Test driven development and glue code
by xdg (Monsignor) on Oct 05, 2005 at 22:57 UTC

    First, you might want to look at How a script becomes a module. The general idea is to convert most of the functionality of a script into a module, and test that as you would a module.

    In general, for testing scripts, you need to write a test file that you can run with prove. In testing scripts, I've found IPC::Run3 to be helpful for driving inputs and outputs in a platform independent way. E.g.

    # file: dummy.pl use strict; use warnings; exit;
    # file: dummy.t use strict; use warnings; use Test::More tests => 1; use IPC::Run3; my @cmd = qw( perl dummy.pl ); ok( run3( \@cmd ), "Running @cmd" );

    prove -v dummy.t produces:

    dummy....1..1 ok 1 - Running perl dummy.pl ok All tests successful. Files=1, Tests=1, 0 wallclock secs ( 0.00 cusr + 0.00 csys = 0.00 C +PU)

    There you go -- a basic script test. All you need to do is keep adding to the test script (or create new ones), run it with prove, and then add code until it works, and you're doing TDD for scripts.

    For helpers to test specific behavior or to help you write your own tests of specific behaviors, you might want to look at:

    • For file properties: Test::File (Actually, search CPAN for modules like "Test::File" and you'll see a variety of possibly useful test helpers for files.)
    • Creating temporary file and directories: File::Temp (You might find my own File::pushd useful in that regard, too.)
    • Driving interactive programs: Test::Expect

    You may need to write new test helpers for things like process killing, or user changes, but you can either build that up with Test::More (or even write new Test:: modules with Test::Builder and release them to CPAN for all to use.)

    -xdg

    Code written by xdg and posted on PerlMonks is public domain. It is provided as is with no warranties, express or implied, of any kind. Posted code may not have been tested. Use of posted code is at your own risk.

Re: Test driven development and glue code
by Perl Mouse (Chaplain) on Oct 05, 2005 at 14:54 UTC
    Well, for a script that should just use a mailbox if it's too large, I'd create a new user. First I create a mailbox that isn't large enough to trigger a move and run the script. If it doesn't do anything, it passes the test. Else it fails. Then I'd increase the mailbox to be just over the limit, and run the script again. If the mailbox is moved (and nothing else happens), the test passes, else the test fails.

    And frankly, that's about all you need to test. Sysadmin scripts often do only one thing - or do one thing only if a condition is true. Which means there aren't many tests to do, and it's probably easier to just test it manually than to spend time figuring out how to automate it.

    Perl --((8:>*
Re: Test driven development and glue code
by eyepopslikeamosquito (Archbishop) on Oct 05, 2005 at 22:11 UTC

    I write a lot of sysadmin scripts. Generally, I try to keep the scripts themselves as short as possible, with most of the work being done in modules. This has a number of advantages:

    • The modules are documented with POD and have tests that exercise them in isolation.
    • When writing a suite of scripts, these scripts share the module code (avoids cut n' paste coding).
    • The scripts themselves are much shorter and therefore easier to understand and maintain.
    • It's quicker to write new scripts because you already have a toolbox of handy routines.
    • This approach scales better, especially when writing many large scripts, maintained by many different people.

    Chapter 9 of the excellent book Perl Testing: A Developer's Notebook contains an interesting tactical trick to better structure scripts for testability. Their idea is to have a one-line script mainline that calls a main() function; that way you can test the script more easily from outside by hooking into the script's main() function. If you are seriously interested in Perl testing, I strongly recommend getting this book.

    See also How a script becomes a module.