题
我们使用 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()")
这不太可读。相反,我想为应用程序、视图和页面编写 Perl DSL。Perl 是否提供某种 DSL 结构,我可以在其中执行以下操作?
App.View2.Page2.Activate();
App.View1.Page2.Click();
其中 App 应是 Application 类的实例。我必须在运行时获取 View2 的对象。
这样的东西怎么用呢?
解决方案
您几乎可以用 Perl 做任何事情。但你必须做一些 奇怪的 让 Perl 使用非 Perl 的语法来执行的东西。
为了准确地处理你所拥有的东西,你将不得不做很多 先进的 技巧,根据定义,这些技巧是不可维护的。你必须:
另一种方法是 源过滤器, ,我可能会投反对票 提及 这个能力。所以我不会完全推荐这种方法给那些 询问 求助。但它就在那里。源过滤器(我已经完成了我的分享)只是您可能认为自己太聪明而不利于自己的领域之一。
不过,如果您对 Perl 作为 DSL“宿主”语言感兴趣,那么 源过滤器 并不完全是禁区。然而,将其限制在你表现出你想做的事情上, Perl6::属性 可能会立即完成您需要的大部分功能。这需要
.
并将它们翻译成 Perl 能理解的“->”。但你仍然可以采取 看 在源头过滤器来理解 什么是 正在幕后进行。我也不想离开这个主题而不建议您可以通过使用 Damian Conway 的方法来缓解生成自己的源过滤器(我建议不要这样做)所带来的很多挫败感 过滤器::简单.
最简单的方法是放弃 '.' 运算符,而是期待 Perl 外观的代码。
App->View2->Page2->Activate(); App->View1->Page2->Click();
App
要么是一个包,要么是一个子包。在当前包中定义或导入,返回一个对象,该对象被祝福到带有View2
子(可能是AUTOLOAD
sub) 返回包的名称或被祝福到包中的引用,它理解Page2
, ,然后最后的回报就会明白Activate
或者Click
. 。(参见 面向对象教程, , 如果你需要。)
其他提示
我建议你停止尝试做怪异的“DSL”的东西,只是写的Perl类来处理你要管理的对象。我建议你考虑使用这一新的驼鹿的Perl的对象系统,虽然传统的Perl OO会就好了。通过Perl文档为OO教程挖;它们是巨大的。
在方法的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
。您也可以滥用自动加载,使所有的方法调用都成功(但注意那些具有特殊含义,就像销毁)。
这将让你的语法。有你的对象产生一个字符串传递给send_command
不应该是困难的。
另外,我不是太熟悉,但你可能想看看穆斯。它可以具有更简单的方法来实现此目的。
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
http://search.cpan.org/dist/Devel-Declare/是现代替代源滤波器,在直接集成到perl的解析器的工作原理,并且是值得期待。
要重写'.'
或使用->
语法可能使用包语法(一种替代::),即创建例如App ::视图2和App ::视图2 ::第2页时视图2 /第2页获得创建包,添加一个AUTOLOAD子到封装委托给一个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();