我们使用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的对象。
如何使用这样的东西?
答案 0 :(得分:20)
你可以在Perl中做任何事情。但是你必须做一些奇怪的东西来让Perl用不是Perl的语法来执行。
要准确处理您所拥有的内容,您必须使用许多高级技巧,根据定义,这些技巧不是可维护的。你必须:
AUTOLOAD
s 另一种方式是source filters,我可能只是为提及此功能而选择了一个downvote。所以我不会向那些询问寻求帮助的人推荐这种方法。但它就在那里。源代码过滤器(我已经完成了我的分享)只是其中一个你可以认为自己太聪明的领域。
但是,如果您对Perl作为DSL“主机”语言感兴趣,那么source filters并非完全不受限制。但是,将此限制为您要显示的内容,Perl6::Attributes可能会完成您现在需要的大部分内容。需要.
并将其翻译为“ - >” Perl会理解的。但是你仍然可以在源过滤器上看外观来理解幕后发生的。
我也不想离开这个主题而不建议你使用Damian Conway Filter::Simple来缓解你可能产生自己的源过滤器(我建议不做)的许多挫败感。 / p>
最简单的事就是放弃'。'运算符,而只是期望看起来像Perl的代码。
App->View2->Page2->Activate();
App->View1->Page2->Click();
App
可以是包也可以是子包。在当前包中定义或导入,它将一个对象返回一个包含View2
sub(可能是AUTOLOAD
sub)的包,该子包返回包的名称或包含在包中的引用,理解Page2
,然后最终从中回复将理解Activate
或Click
。 (如果需要,请参阅OO tutorial。)
答案 1 :(得分:6)
我建议你不要尝试做怪异的“DSL”东西,只需编写Perl类来处理你想要管理的对象。我建议您考虑使用新的Moose Perl对象系统,尽管传统的Perl OO会很好。深入了解OO教程的Perl文档;他们很棒。
答案 2 :(得分:4)
perl5中的方法调用使用->
而不是.
,所以它看起来像App->View2->Page2->Activate()
或$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。它可能有更简单的方法来实现这一目标。
答案 3 :(得分:4)
这是另一次尝试。 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
答案 4 :(得分:1)
http://search.cpan.org/dist/Devel-Declare/是源过滤器的现代替代品,可直接集成到perl解析器中,值得一看。
答案 5 :(得分:0)
覆盖'.'
或使用->
语法的替代方法可能是使用包语法(::),即在View2 / Page时创建App :: View2和App :: View2 :: Page2等包2创建,将一个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();