Pregunta

Utilizamos Perl para la automatización de pruebas de GUI. Ha sido muy exitoso. Hemos escrito un lenguaje DSL muy ligero para pruebas de GUI. El DSL es muy similar a un modelo de objeto.

Por ejemplo, tenemos un objeto Aplicación en la raíz. Cada hoja de propiedades en la aplicación es un objeto Ver. Cada página debajo de la página se llama objeto de página en sí. Desde Perl enviamos comandos a una aplicación GUI y la GUI interpreta el comando y responde al comando muy bien. Para enviar un comando hacemos lo siguiente:

socket_object->send_command("App.View2.Page2.Activate()")
socket_object->send_command("App.View1.Page3.OKBtn.Click()")

Esto no es muy legible. En cambio, quiero escribir un DSL de Perl para aplicación, vista y página. ¿Perl proporciona algún tipo de estructura DSL donde puedo hacer lo siguiente?

App.View2.Page2.Activate();
App.View1.Page2.Click();

Donde la aplicación será una instancia de la clase Aplicación. Tengo que obtener el objeto de View2 en tiempo de ejecución.

¿Cómo usar tales cosas?

¿Fue útil?

Solución

Puedes hacer casi cualquier cosa en Perl. Pero tienes que hacer algunas cosas extrañas para que Perl funcione con una sintaxis que simplemente no es Perl.

  • Para manejar exactamente lo que tienes allí, tendrías que hacer muchos trucos avanzados , que por definición no son tan fáciles de mantener. Tendrías que:

    • sobrecargar el operador de concatenación '.' (requiere una referencia bendecida)
    • desactive restricciones o cree un AUTOLOAD subs para permitir esas palabras vacías; por supuesto, puede escribir subs para todas las palabras que desea usar (o use barewords module).
    • posiblemente, cree múltiples paquetes, con múltiples AUTOLOAD s
  • Otra forma es filtros fuente , probablemente pueda obtener un voto negativo solo por mencionar esta capacidad. Por lo tanto, no recomendaría este enfoque exactamente a las personas que están pidiendo ayuda. Pero está ahí afuera. Los filtros de origen (y he hecho mi parte) son solo una de esas áreas en las que puedes pensar que eres demasiado inteligente para tu propio bien.

    Aún así, si está interesado en Perl como DSL "host" idioma, entonces filtros de origen no son exactamente fuera de los límites. Sin embargo, limitando esto a lo que muestra que quiere hacer, Perl6 :: Atributos probablemente hará la mayor parte de lo que necesitaría de inmediato. Tomaría el . y los traduciría al " - > " que Perl entendería. Pero aún puede echar un vistazo a los filtros de origen para comprender lo que está sucediendo detrás de escena.

    Tampoco quiero dejar este tema sin sugerir que mucha de la frustración que podrías tener generando tu propio filtro de origen (que aconsejo que NO hagas) se alivia utilizando Filter :: Simple .

  • Lo más simple es renunciar al '.' operador y simplemente en lugar de esperar el código de aspecto Perl.

    App->View2->Page2->Activate(); 
    App->View1->Page2->Click();
    

    App sería un paquete o un sub. Ya sea definido en el paquete actual o importado que devuelve un objeto bendecido en un paquete con un sub View2 (posiblemente un AUTOLOAD sub) que devuelve el nombre de un paquete o una referencia bendecida en un paquete, que comprende Page2 , y luego finalmente el retorno de eso comprendería Activar o Click . (Consulte el tutorial OO , si lo necesita).

Otros consejos

Te recomiendo que dejes de intentar hacer "DSL" extraño cosas y simplemente escriba clases de Perl para manejar los objetos que desea administrar. Le recomiendo que busque usar el nuevo sistema de objetos Moose Perl para esto, aunque Perl OO tradicional estaría bien. Examine la documentación de Perl para los tutoriales de OO; son geniales.

Las llamadas al método en perl5 usan - > no . , por lo que se verá como App- > View2- > Page2- > Activate () o $ App- > View2- > Page2- > Active () a menos que haga algo realmente interesante (por ejemplo, un filtro fuente). Suponiendo que esté bien, puede usar cosas normales de Perl OO.

Ahora, la siguiente parte de lo que necesita es crear los métodos en tiempo de ejecución. En realidad, esto es bastante simple:

sub _new_view {
    my ($view, $view_num);

    # ...
    # ... (code to create $view object)
    # ...

    my $sym = "App::View$view_num";
    *$sym = sub { return $view }; # can also use Symbol package
}

Alternativamente, si desea crear los métodos solo cuando se los llama, eso es lo que AUTOLOAD hace. También puede abusar de la carga automática para que todas las llamadas a métodos tengan éxito (aunque tenga cuidado con las que tengan significados especiales, como DESTRUIR).

Esto te dará la sintaxis. Hacer que sus objetos generen una cadena para pasar a send_command no debería ser tan difícil.

Además, no estoy demasiado familiarizado con él, pero es posible que desee consultar Moose . Puede tener formas más fáciles de lograr esto.

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

package RemoteAppScript;
use Filter::Simple;    # The basis of many a sane source filter
use Smart::Comments;   # treat yourself and install this if you don't have 
                       # it... or just comment it out.

# Simple test sub
sub send_command { 
    my $cmd = shift;
    print qq(Command "$cmd" sent.\n);
    return;
}

# The list of commands
my @script_list;

# The interface to Filter::Simple's method of source filters.
FILTER { 
    # Save 

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

use RemoteAppScript;
App.View2.Page2.Activate();
App.View1.Page2.Click();

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

App.View2.Page2.Activate();
App.View1.Page2.Click();

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

#!/bin/perl -w 

my $script = do { 
    local $/;
    <ARGV>;
};

$script =~ s/^\s*#.*$//m;

foreach my $command ( 
    grep { length() } map  { s/^\s+|\s+$//g; 

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

package RemoteAppScript;
use Filter::Simple;    # The basis of many a sane source filter
use Smart::Comments;   # treat yourself and install this if you don't have 
                       # it... or just comment it out.

# Simple test sub
sub send_command { 
    my $cmd = shift;
    print qq(Command "$cmd" sent.\n);
    return;
}

# The list of commands
my @script_list;

# The interface to Filter::Simple's method of source filters.
FILTER { 
    # Save 

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

use RemoteAppScript;
App.View2.Page2.Activate();
App.View1.Page2.Click();

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

App.View2.Page2.Activate();
App.View1.Page2.Click();

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

perl run_remote_script.pl remote_app_script.ras

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*>, because Filter::Simple doesn't like you reading more than once. my $mod =

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*>; # v-- Here a Smart::Comment. ### $mod # Allow for whole-line perl style comments in the script $mod =~ s/^\s*#.*$//m; # 1. Break the package up into commands by split # 2. Trim the strings, if needed # 3. lose the entries that are just blank strings. @script_list = grep { length } map { s/^\s+|\s+$//g;

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*> } split /;/, $mod ; ### @script_list # Replace the whole script with a command to run the steps.

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*> = __PACKAGE__ . '::run_script();'; # PBP. return; }; # Here is the sub that performs each action. sub run_script { ### @script_list foreach my $command ( @script_list ) { #send_command( $command ); socket_object->send_command( $command ); } } 1;

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*> } split /;/, $script ) { socket_object->send_command( $command ); }

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*>, because Filter::Simple doesn't like you reading more than once. my $mod =

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*>; # v-- Here a Smart::Comment. ### $mod # Allow for whole-line perl style comments in the script $mod =~ s/^\s*#.*$//m; # 1. Break the package up into commands by split # 2. Trim the strings, if needed # 3. lose the entries that are just blank strings. @script_list = grep { length } map { s/^\s+|\s+$//g;

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*> } split /;/, $mod ; ### @script_list # Replace the whole script with a command to run the steps.

Filtro de origen DSL

Aquí hay otro intento. skiphoppy tiene un punto, pero en una segunda mirada, me di cuenta de que (hasta ahora) no estabas preguntando mucho que era tan complejo. Solo desea tomar cada comando y decirle al servidor remoto que lo haga. No es perl el que tiene que entender los comandos, es el servidor.

Entonces, elimino algunas de mis advertencias sobre los filtros de origen y decidí mostrarte cómo se puede escribir una simple. Una vez más, lo que estás haciendo no es tan complejo, y mi '' filtrado '' a continuación es bastante fácil.

<*>

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*> = __PACKAGE__ . '::run_script();'; # PBP. return; }; # Here is the sub that performs each action. sub run_script { ### @script_list foreach my $command ( @script_list ) { #send_command( $command ); socket_object->send_command( $command ); } } 1;

Debería guardar esto en RemoteAppScript.pm en algún lugar donde su perl pueda encontrarlo. (pruebe perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' si necesita saber dónde.)

Entonces puede crear un " perl " archivo que tiene esto:

<*>

Sin embargo

No hay una razón real por la que no pueda leer un archivo que contenga comandos del servidor. Eso arrojaría la llamada FILTER . Habrías

<*>

en su archivo de script, y su archivo perl se vería más así:

<*>

Y llámalo así:

<*>

http://search.cpan.org/dist/Devel-Declare/ es una alternativa moderna a los filtros fuente que funciona para integrarse directamente en el analizador perl, y merece la pena echarle un vistazo.

Una alternativa para anular '.' o usar la sintaxis - > podría estar usando la sintaxis de paquete (: :), es decir, crear paquetes como App :: View2 y App :: View2 :: Page2 cuando se crea View2 / Page 2, agregando un subcargador AUTOLOAD al paquete que delega en una aplicación :: View :: Page o App :: View, algo así:

En su aplicación / DSL.pm:

package App::DSL;
use strict; 
use warnings;
# use to avoid *{"App::View::$view::method"} = \&sub and friends
use Package::Stash;

sub new_view(%);
our %views;

# use App::DSL (View1 => {attr1 => 'foo', attr2 => 'bar'}); 
sub import {
    my $class = shift;
    my %new_views = @_ or die 'No view specified';

    foreach my $view (keys %new_views) {
            my $stash = Package::Stash->new("App::View::$view");
        # In our AUTOLOAD we create a closure over the right
        # App::View object and call the right method on it
        # for this example I just used _api_\L$method as the
        # internal method name (Activate => _api_activate)
        $stash->add_package_symbol('&AUTOLOAD' =>  
            sub {  
                our $AUTOLOAD;
                my ($method) = 
                   $AUTOLOAD =~ m{App::View::\Q$view\E::(.*)};
                my $api_method = "_api_\L$method";
                die "Invalid method $method on App::View::$view"
                   unless my $view_sub = App::View->can($api_method);
                my $view_obj = $views{$view}
                    or die "Invalid View $view";
                my $sub = sub {
                        $view_obj->$view_sub();
                };
                     # add the function to the package, so that AUTOLOAD
                     # won't need to be called for this method again
                $stash->add_package_symbol("\&$method" => $sub);
                goto $sub;
            });
        $views{$view} = bless $new_views{$view}, 'App::View';
    }
}

package App::View;

# API Method App::View::ViewName::Activate;
sub _api_activate {
    my $self = shift;
    # do something with $self here, which is the view 
    # object created by App::DSL
    warn $self->{attr1};
}

1;

y en tu script:

use strict;
use warnings;
# Create App::View::View1 and App::View::View2
use App::DSL (View1 => {attr1 => 'hello'}, View2 => {attr1 => 'bye'});
App::View::View1::Activate();
App::View::View2::Activate();
App::View::View1::Activate();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top