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

Monks,

In considering how to automate testing on our (increasingly huge) application here at $work, I've been toying with creating a "script runner" in Perl, which would allow me (or my staff) to write scripts using Perl and some functions specific to driving our application, but without using (as much) object notation to manipulate the application. I haven't gotten any farther than a proof-of-concept, but I hope to pick your collective brains before I fall too far down the rabbit hole.

Here's a module which I'm using to test my "script runner" idea. If I were to move ahead, this would be significantly more involved, but I think it will help you get the idea what I'm talking about:

#! /usr/local/bin/perl package Product::Driver; use strict; use warnings; use Moose; has counter => ( isa => 'Int', default => 0, reader => 'value' ); sub add_one { my ( $self ) = @_; return ++$self->{'counter'}; } sub subtract_one { my ( $self ) = @_; return --$self->{'counter'}; } sub add_number { my ( $self, $amount ) = @_; return $self->{'counter'} += $amount; } sub subtract_number { my ( $self, $amount ) = @_; return $self->{'counter'} -= $amount; } sub reset_counter { my ( $self ) = @_; return $self->{'counter'} = 0; } 1;

Here's the "script runner" module that would read scripts and execute them

#! /usr/local/bin/perl package ScriptRunner; use strict; use warnings; use Product::Driver; use MooseX::Singleton; has driver => ( isa => 'Product::Driver' ); #Not yet implemented #has recorder => ( does => 'Tester::Recorder', required => 1 ); sub BUILD { my ( $self ) = @_; $self->_init_driver(); } sub _init_driver { my $self = ref($_[0]) ? shift : ScriptRunner->new(); $self->{'driver'} = Product::Driver->new(); } sub close_driver { my $self = ref($_[0]) ? shift : ScriptRunner->new(); $self->{'driver'} = undef; } sub start_driver { my $self = ref($_[0]) ? shift : ScriptRunner->new(); if ( $self->{'driver'} == undef ) { $self->_init_driver(); } } sub restart_driver { my $self = ref($_[0]) ? shift : ScriptRunner->new(); $self->close_driver(); $self->start_driver(); } sub check_success { my ( $self ) = ScriptRunner->new(); my $val = ( $_[0] ne '' ) ? $_[0] : 0; my $msg = $_[1] ? $_[1] : "Unknown Event"; # Eventually, instead of printing the result, there would be a # recorder module that would be available to record the test # results in some convenient, meaningful way. But for now... printf "%-20s%s\n", $val ? "PASSED TEST" : "FAILED TEST", $msg; } sub add_one { my ( $self ) = ScriptRunner->new(); return $self->{'driver'}->add_one; } sub add_number { my ( $self ) = ScriptRunner->new(); my ( $amount ) = @_; return $self->{'driver'}->add_number($amount); } sub subtract_one { my ( $self ) = ScriptRunner->new(); return $self->{'driver'}->subtract_one; } sub subtract_number { my ( $self ) = ScriptRunner->new(); my ( $amount ) = @_; return $self->{'driver'}->subtract_number($amount); } sub value { my ( $self ) = ScriptRunner->new(); return $self->{'driver'}->value; } sub reset_counter { my ( $self ) = ScriptRunner->new(); return $self->{'driver'}->reset_counter; } sub do_script { my $self = ref($_[0]) ? shift : ScriptRunner->new(); my ( $file ) = @_; if ( ! -f $file ) { die "$file not found!" } my $res = do $file; if ( $@ ) { die "Can't do $file\n", $@ } return $res; } 1;

A simple app that creates a ScriptRunner object and then feeds the filenames of scripts to be executed (specified on the command line).

#! /usr/local/bin/perl use strict; use warnings; use lib "./lib"; use ScriptRunner; my $runner = ScriptRunner->new(); # Do any necessary environmental setup here. Since this is # just a demo, there's nothing really to do. foreach my $f ( @ARGV ) { print "Running script file: $f\n"; $runner->do_script($f) }

And a sample script:

my $string = ''; $string .= add_one(); $string .= " "; $string .= add_number(50); $string .= " "; $string .= subtract_one(); $string .= " "; $string .= subtract_number(45); $string .= " "; $string .= value(); check_success ( $string eq "1 51 50 5 5", "This is a comment" ); return ( $string );

The code probably isn't the best at this point, I've only been trying to decide if I'm moving forward with this.

So, with all that in mind, here are my questions:

  1. Am I insane for trying to do this? Does this seem a reasonable thing to do, or should I just take a step back, remove the ScriptRunner idea, and buy a copy of Learning Perl for the testing staff?
  2. If this isn't a completely insane thing to do, does my approach seem reasonable? To be honest, it seems kind of unwieldy to add a function to the ScriptRunner for every function I add to the Product::Driver (there could end up being quite a lot of functions), but I'm not sure how else to proceed. Is there a better way to accomplish what I'm trying to do?

Thanks for your time and feedback.

Regards,

Bald Man Tom

Replies are listed 'Best First'.
Re: On the merits of implementing a script running application
by moritz (Cardinal) on Apr 03, 2008 at 16:45 UTC
    Why do you want to hide the object orientation from the runner scripts? If that's the most natural interface to your application, why mask it?

    In general it's a good idea to have some API and expose it to the outside world, but I don't see the benefit of having wrapper subs

    The ScriptRunner contains lots of duplicate code that you should remove (at least if you stay with your current approach):

    # instead of #sub value #{ # my ( $self ) = ScriptRunner->new(); # return $self->{'driver'}->value; #} sub make_wrapper { my $name = shift; return sub { ScriptRunner->new()->{'driver}->$name(@_); } } *value = make_wrapper('value'); *reset_counter = make_wrapper('reset_counter); ... # (untested)

    You could also use autoloading (see perlsub, section "Autoloading") to achieve the same thing.

      Hello moritz, and thanks for your reply.

      Why do you want to hide the object orientation from the runner scripts? If that's the most natural interface to your application, why mask it?

      Well.... I did mention I was falling down a rabbit hole :) I've been seduced by scripting tools, particularly Marathon which provides extensions for Jython. I saw that, wondered a) how hard it would be and b) how it might "feel" to use it if I could do it, and decided to try. I'm not at all married to hiding the object orientation, I just thought that it might be easier for my fellow testers to write click_folder($folder_id) than $driver->click_folder($folder_id), particularly since they don't (currently) write Perl. In fact, before I go too much further, I'll probably show them sample scripts, one hiding the object orientation and another showing it, and ask them which they'd prefer to work with.

      # instead of #sub value #{ # my ( $self ) = ScriptRunner->new(); # return $self->{'driver'}->value; #} sub make_wrapper { my $name = shift; return sub { ScriptRunner->new()->{'driver}->$name(@_); } } *value = make_wrapper('value'); *reset_counter = make_wrapper('reset_counter);

      Thanks! I figured there had to be a better way to do what I was trying to, but couldn't come up with it. I'll also read about autoloading.

      Regards,

      Bald Man Tom

Re: On the merits of implementing a script running application
by samtregar (Abbot) on Apr 03, 2008 at 17:23 UTC
    Sounds like a fun project, but maybe not very useful. I nearly wrote a shell-style scripting language for Krang but perrin managed to talk me out of it before I could get started. He pointed out, correctly, that it wouldn't offer much utility over the existing OO API. My vague hope that our sys-admins would write their own data-manipulation scripts wasn't actually very likely.

    Who are the intended users of your tool?

    -sam

      Hi samtregar,

      Sounds like a fun project, but maybe not very useful.
      Who are the intended users of your tool?

      I'm gathering that it may not be terribly useful. It's been a learning experience for me, though so it's not a total loss. As for my intended users, mostly our testing staff, though I wouldn't complain if our dev staff found it useful, too. None of them know Perl at this point (we're a Java/Jython shop) but I have a feeling that it'd be easier to provide them with sufficient examples and a few Perl books in the long run. It would also likely reduce the amount of errors introduced by my own poor coding...

Re: On the merits of implementing a script running application
by stvn (Monsignor) on Apr 05, 2008 at 02:55 UTC

    I am wondering if you have seen MooseX::Getopt yet? It makes it very easy to map your objects to scripts, which might actually help here. Also, since you are already using Moose a lot of what you have written above could actually be done with meta-programming.

    -stvn

      Hi stvn,

      Nope, I hadn't yet seen MooseX::Getopt, but I'll check it out. Thanks! I'm only just now getting into using Moose itself, there's lots more to explore, including meta-programming. Thanks for the tips!