Domanda

Usiamo Perl per l'automazione del test della GUI. Ha avuto molto successo. Abbiamo scritto un linguaggio DSL molto leggero per i test della GUI. Il DSL è molto simile a un modello a oggetti.

Ad esempio, abbiamo un oggetto Application alla radice. Ogni finestra delle proprietà nell'applicazione è un oggetto Visualizza. Ogni pagina sotto la pagina è chiamata oggetto Pagina stesso. Da Perl inviamo i comandi a un'applicazione GUI e la GUI interpreta il comando e risponde bene al comando. Per inviare un comando, facciamo quanto segue:

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

Questo non è molto leggibile. Invece, voglio scrivere un DSL Perl per app, vista e pagina. Perl fornisce una sorta di struttura DSL in cui posso fare quanto segue?

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

Dove l'app deve essere un'istanza della classe Application. Devo ottenere l'oggetto di View2 in fase di esecuzione.

Come usare queste cose?

È stato utile?

Soluzione

Puoi fare quasi tutto in Perl. Ma devi fare alcune cose strane per far funzionare Perl con una sintassi che non è Perl.

  • Per gestire esattamente quello che hai lì, dovresti fare molti trucchi avanzati , che per definizione non sono così mantenibili. Dovresti:

    • overload l'operatore di concatenazione "." (richiede un riferimento benedetto)
    • disattiva strictures o crea un AUTOLOAD sottotitoli per consentire quelle parole spoglie - ovviamente, potresti scrivere sottotitoli per tutte le parole che vuoi usare (o usare barewords modulo).
    • possibilmente, creare più pacchetti, con più AUTOLOAD s
  • Un altro modo è filtri sorgente , posso probabilmente raccogliere un downvote solo per menzionare questa capacità. Quindi non consiglierei esattamente questo approccio per le persone che chiedono aiuto. Ma è là fuori. I filtri di origine (e ho fatto la mia parte) sono solo una di quelle aree in cui puoi pensare di essere troppo intelligente per il tuo bene.

    Tuttavia, se sei interessato a Perl come DSL " host " lingua, quindi filtri sorgente non sono esattamente vietato. Tuttavia, limitandolo a ciò che mostri di voler fare, Perl6 :: Attributes probabilmente farà la maggior parte di ciò di cui avresti bisogno subito. Richiederebbe il . e li tradurrebbe in " - > " che Perl avrebbe capito. Ma puoi ancora dare un'occhiata a ai filtri sorgente per capire cosa sta succedendo dietro le quinte.

    Inoltre, non voglio abbandonare questo argomento senza suggerire che molta della frustrazione che potresti avere nel generare il tuo filtro sorgente (cosa che consiglio di NON fare) è alleviata usando Filtro :: Semplice .

  • La cosa più semplice è rinunciare al '.' operatore e invece si aspettano un codice dall'aspetto Perl.

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

    App sarebbe un pacchetto o un sub. Definito nel pacchetto corrente o importato che restituisce un oggetto benedetto in un pacchetto con un sub View2 (possibilmente un AUTOLOAD sub) che restituisce il nome di un pacchetto o un riferimento benedetto in un pacchetto, che comprende Page2 e quindi infine il ritorno da ciò comprenderebbe Activate o Click . (Vedi il tutorial OO , se necessario.)

Altri suggerimenti

Ti consiglio di smettere di provare a fare "DSL" strano " roba e basta scrivere classi Perl per gestire gli oggetti che si desidera gestire. Vi consiglio di esaminare l'uso del nuovo sistema di oggetti Moose Perl per questo, sebbene il tradizionale Perl OO andrebbe bene. Scorri la documentazione di Perl per le esercitazioni OO; sono fantastici.

Le chiamate di metodo in perl5 usano - > non . , quindi sembrerà App- > View2- > Page2- > Attiva () o $ App- > View2- > Page2- > Active () a meno che tu non faccia qualcosa di veramente interessante (ad esempio, un filtro sorgente). Supponendo che sia OK, puoi usare le normali cose Perl OO.

Ora, la parte successiva di ciò che ti serve è creare i metodi in fase di esecuzione. Questo in realtà è abbastanza semplice:

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
}

In alternativa, se si desidera creare i metodi solo quando vengono chiamati, è ciò che fa AUTOLOAD . Puoi anche abusare del caricamento automatico per far sì che tutte le chiamate al metodo abbiano successo (anche se fai attenzione a quelle con significati speciali, come DISTRUGGI).

Questo ti darà la sintassi. Fare in modo che i tuoi oggetti generino una stringa da passare a send_command non dovrebbe essere così difficile.

Inoltre, non ne ho troppa familiarità, ma potresti voler dare un'occhiata a Moose . Potrebbe avere dei modi più semplici per farlo.

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

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 sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

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

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

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

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

#!/bin/perl -w 

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

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

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

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

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 sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

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

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

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

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

perl run_remote_script.pl remote_app_script.ras

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

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

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

<*>; # 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 sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

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

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

<*> = __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;

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

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

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

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

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

<*>; # 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 sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

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

Filtro sorgente DSL

Ecco un altro tentativo. skiphoppy ha ragione, ma a una seconda occhiata, ho notato che (finora) non stavi chiedendo molto che fosse così complesso. Vuoi solo prendere ogni comando e dire al server remoto di farlo. Non è perl che deve capire i comandi, è il server.

Quindi, rimuovo alcuni dei miei avvertimenti sui filtri di origine e ho deciso di mostrarti come uno semplice può essere scritto. Ancora una volta, quello che stai facendo non è così complesso, e il mio "filtro" " sotto è abbastanza facile.

<*>

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

<*> = __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;

Dovresti salvarlo in RemoteAppScript.pm da qualche parte dove il tuo perl può trovarlo. (prova perl -MData :: Dumper -e 'print Dumper (\ @INC), " \ n "' se devi sapere dove.)

Quindi puoi creare un " perl " file che ha questo:

<*>

Tuttavia

Non esiste alcun motivo reale per cui non è possibile leggere un file che contiene i comandi del server. Ciò eliminerebbe la chiamata FILTER . Avresti

<*>

nel tuo file di script e il tuo file perl sarebbe più simile a questo:

<*>

E chiamalo così:

<*>

http://search.cpan.org/dist/Devel-Declare/ è una moderna alternativa ai filtri di origine che lavora per integrarsi direttamente nel parser perl e vale la pena dare un'occhiata.

Un'alternativa alla sostituzione di '.' o all'utilizzo della sintassi - > potrebbe essere la sintassi dei pacchetti (: :), ovvero la creazione di pacchetti come App :: View2 e App :: View2 :: Page2 quando View2 / Page 2 viene creato, aggiungendo un sub AUTOLOAD al pacchetto che delega a un'app :: View :: Page o App :: View method, qualcosa del genere:

Nella tua app / 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;

e nel tuo 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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top