13

我们使用 Perl 进行 GUI 测试自动化。它非常成功。我们为 GUI 测试编写了一种非常轻量级的 DSL 语言。DSL 与对象模型非常相似。

例如,我们在根目录中有一个 Application 对象。应用程序中的每个属性表都是一个 View 对象。页面下的每个页面都称为 Page 对象本身。我们从 Perl 向 GUI 应用程序发送命令,GUI 解释命令并很好地响应命令。要发送命令,我们执行以下操作:

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

这不是很可读。相反,我想为 App、View 和 Page 编写 Perl DSL。Perl 是否提供某种 DSL 结构,我可以在其中执行以下操作?

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

其中 App 应为 Application 类的实例。我必须在运行时获取 View2 的对象。

这样的东西怎么用?

4

6 回答 6

21

你几乎可以在 Perl 中做任何事情。但是你必须做一些奇怪的事情才能让 Perl 使用不是 Perl 的语法来执行。

  • 要准确处理那里的内容,您将不得不使用许多高级技巧,这些技巧根据定义并不那么可维护。你必须:

    • 重载连接运算符 '.' (需要有福的参考)
    • 关闭限制或创建AUTOLOAD subs 以允许使用这些裸词 - 当然,您可以为所有要使用的单词编写 subs(或使用barewords模块)。
    • 可能,创建多个包,多个AUTOLOADs
  • 另一种方法是源过滤器,我可能会因为提及此功能而投反对票。因此,对于寻求帮助的人,我不会完全推荐这种方法。但它就在那里。源过滤器(我已经完成了我的分享)只是您认为自己太聪明而不利于自己的那些领域之一。

    不过,如果您对将 Perl 作为 DSL“宿主”语言感兴趣,那么源过滤器并不是完全不受限制的。但是,将其限制在您想要做的事情上,Perl6::Attributes可能会立即完成您需要的大部分工作。它将把.它们翻译成 Perl 可以理解的“->”。但是您仍然可以查看源过滤器以了解幕后发生的事情。

    我也不想离开这个话题,而不建议使用 Damian Conway 的Filter::Simple来缓解您在生成自己的源过滤器时可能遇到的很多挫折(我建议不要这样做) 。

  • 最简单的事情是放弃'。运算符,而只是期望看起来像 Perl 的代码。

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

    App将是一个包或一个子。在当前包中定义或导入,它返回一个对象被祝福到一个带有View2子(可能是一个AUTOLOAD子)的包中,它返回一个包的名称或一个被祝福到包中的引用,它可以理解Page2,然后最后从那个返回会理解ActivateClick。(如果需要,请参阅OO 教程。)

于 2008-12-05T06:30:48.467 回答
6

我建议你放弃尝试做怪异的“DSL”的东西,只写 Perl 类来处理你想要管理的对象。我建议您考虑为此使用新的 Moose Perl 对象系统,尽管传统的 Perl OO 就可以了。深入研究 OO 教程的 Perl 文档;他们都是伟大的。

于 2008-12-05T15:13:14.543 回答
4

perl5 中的方法调用使用->not .,所以它看起来像App->View2->Page2->Activate()or$App->View2->Page2->Active()除非你做了一些非常有趣的事情(例如,源过滤器)。假设没问题,您可以使用普通的 Perl OO 东西。

现在,您需要的下一部分是在运行时创建方法。这实际上相当简单:

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
}

或者,如果您只想在调用它们时创建方法,那就是AUTOLOAD这样。您还可以滥用自动加载来使所有方法调用成功(但要注意具有特殊含义的方法,例如 DESTROY)。

这将为您提供语法。让您的对象生成要传递的字符串send_command应该不是那么困难。

另外,我对它不太熟悉,但你可能想看看Moose。它可能有更简单的方法来实现这一点。

于 2008-12-05T06:10:19.883 回答
4

DSL 源过滤器

这是另一个尝试。skiphoppy 说得有道理,但再看一遍,我注意到(到目前为止)你并没有问太多复杂的问题。您只想获取每个命令并告诉远程服务器执行此操作。必须理解命令的不是perl,而是服务器。

因此,我删除了一些关于源过滤器的警告,并决定向您展示如何编写一个简单的过滤器。同样,您所做的并不那么复杂,我在下面的“过滤”非常简单。

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 $_, because Filter::Simple doesn't like you reading more than once.
    my $mod = $_;

    # 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; $_ } 
          split /;/, $mod
        ;
    ### @script_list

    # Replace the whole script with a command to run the steps.
    $_ = __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;

你需要把它保存RemoteAppScript.pm在你的 perl 可以找到它的地方。(perl -MData::Dumper -e 'print Dumper( \@INC ), "\n"'如果您需要知道在哪里,请尝试。)

然后你可以创建一个“perl”文件,其中包含:

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

然而

没有真正的理由不能读取包含服务器命令的文件。那会打断FILTER电话。你将会拥有

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

在你的脚本文件中,你的 perl 文件看起来更像这样:

#!/bin/perl -w 

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

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

foreach my $command ( 
    grep { length() } map  { s/^\s+|\s+$//g; $_ } split /;/, $script 
) { 
    socket_object->send_command( $command );
}

并这样称呼它:

perl run_remote_script.pl remote_app_script.ras
于 2008-12-05T23:03:46.903 回答
1

http://search.cpan.org/dist/Devel-Declare/是源过滤器的现代替代品,可直接集成到 perl 解析器中,值得一看。

于 2010-10-21T14:50:47.973 回答
0

'.'覆盖或使用语法的替代方法->可能是使用包语法 (::),即在创建 View2 / Page 2 时创建 App::View2 和 App::View2::Page2 等包,将 AUTOLOAD 子添加到委托的包中到 App::View::Page 或 App::View 方法,如下所示:

在您的 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;

在你的脚本中:

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();
于 2010-10-21T17:21:00.100 回答