用java构建企业级自动化框架(首篇-制定测试者使用语言3)_eclipse自动化用企业级的吗-程序员宅基地

技术标签: 语言  java  行业应用  selenium  测试  编程语言  框架  

这个是我后来写的一本书,http://www.ituring.com.cn/minibook/10775。这个是我后来找到的自动化完美解决方案。


接下来对数据库的测试也提供一种编写思路,具体如何实现这个就不细说了。

     <Status name='get_ponumber' expected='0' timeout="600" compare="&gt;">

<Sql name="sql_ponum" db_type="oms_ff" params="testjingdongcom.productId">


SELECT DISTINCT p.po_no FROM wff_po_line p, wff_line_item l WHERE p.co_order_no=[orderNo]

AND l.order_no = p.co_order_no AND l.item_id = :1 AND l.line_no = p.co_line_no


                </Sql>

</Status>


当然,除了页面操作和数据库以外,自己系统特有的业务也可以自己定义并自动化,比如jms远程调用,发邮件文件短线等等。

说了这么多,大家有没有发现这种类型脚本有一个问题,他们执行的每一个节点都是一个类,而测试人员写这脚本的时候,可能还需要开发人员告诉他们这些类是干什么的,传哪些参数。测试人员没有主动权在脚本中编写自己的流程步骤。因为流程都封装在类里面的方法里,同时,把流程步骤封装到java类里会出现类和包越来越膨胀。到最后可能会膨胀到无法维护。那怎么样才能解决这些问题呢,那就是以把操作的流程步骤到放在脚本里面去。而不是java类里。

下面给出一个思路,每个java类只执行一个原子操作,如何组装这些原子操作是放在脚本里面去执行的。


先看代码


第一,我先定义一个执行类负责执行页面操作流程的所有方法,比如点击,输入,选择等等。


public class ActionExecutor {






private static Map<String, Selenium> browsers = new HashMap<String, Selenium>();


private static Selenium getBrowser() {
String threadName = Thread.currentThread().getName();
Selenium browser = browsers.get(threadName);
return browser;
}


public static Selenium getSelenium() {
Selenium browser = getBrowser();
return browser;


}


public static void open(String url, String tip) {
Selenium browser = getBrowser();
if (browser != null) {
try {
browser.open(url);


browser.waitForPageToLoad(String
.valueOf(LambConstants.DEFAULT_TIMEOUT));
browser.windowMaximize();
} catch (Exception e) {
testLog.info("open page==> url=" + url + " couldn't open-->"
+ tip);
}
}
}


public static void type(final String locator, final String value,
final String tip) throws Exception {
Poller.until(new Condition() {


public void doCommands() {
Selenium browser = getBrowser();
log.debug("type command locator=" + locator);
if (browser != null) {
try {
browser.type(locator, value);
testLog.info("input command==> value=" + value
+ " input to TextArea " + locator
+ " success-->" + tip);
logger.info("LogBaseTest");
} catch (Exception e) {
testLog.info("iput command==> value=" + value
+ " input to TextArea " + locator
+ " failure-->" + tip);
}
}
}


});
}


public static void click(String locator, String tip) {
Selenium browser = getBrowser();
if (browser != null) {
log.debug("click command locator=" + locator);
try {
browser.click(locator);
testLog.info("click Button==> locator=" + locator
+ " execute success-->" + tip);
} catch (Exception e) {
testLog.info("click Button==> locator=" + locator
+ " execute failure-->" + tip);
}
}
}


public static void clickAndWait(String locator, String timeout, String tip) {
Selenium browser = getBrowser();
if (browser != null) {
log.debug("click and wait command locator=" + locator);
try {
browser.click(locator);
browser.waitForPageToLoad(timeout);
testLog.info("click Button==> locator=" + locator
+ " execute success-->" + tip);
} catch (Exception e) {
testLog.info("click Button==> locator=" + locator
+ " execute failure-->" + tip);
}
}
}

这里是只实现一部分selenium的操作。

那现在我们如何使用它呢?

第一,开发的老规矩,先定义一个接口去制定类的行为。


public interface Action {

public void validate() throws Exception;

public Object execute() throws Exception;
}


好的,下面就简单了,去实现它的所有实现类

比如click等等

public class ClickAction  implements Action {


private String locator;

public Object execute() throws Exception {
ActionExecutor.click(locator,tip);
return null;
}


public void validate() throws Exception {
}


public String getLocator() {
return locator;
}


public void setLocator(String locator) {
this.locator = locator;
}
}


比如选择

public class SelectAction implements Action{
private String locator;
private String value;
public void validate() throws Exception {

}


public Object execute() throws Exception {
ActionExecutor.setSelect(this.locator, this.value);
return null;
}


public String getLocator() {
return locator;
}


public void setLocator(String locator) {
this.locator = locator;
}


public String getValue() {
return value;
}


public void setValue(String value) {
this.value = value;
}



}


这时候你看下面调用脚本,即xml,会发现所有调用流程是放在xml去执行的。(脚本的执行方法和前面说的反射执行方式是一样的)

<StartBrowserAction/>
<OpenAction url="http://www.google.com.hk"/>
<TypeAction locator="q" value="java"/>
<TypeAction locator="q" value="C++"/>
<SleepAction seconds="5"/>

             <ClickAction locator="btnG"/>

<StopBrowserAction/>


这样就把调用逻辑写在脚本里面去了,当然这里面还有些难点,比如把流程封装在代码中非常容易实现的if 选择和for循环在这里是如何实现。对各种java类库比如字符串方法的使用能否做到和程序员写代码一样方便都应该考虑进去。


好的,我们把这两种方法脚本写法比较一下分析它们的优缺点,

1,流程封装到java类里,各种逻辑和技术等容易在代码中实现,但会造成java类膨胀,此框架会和测试人员分离,测试人员无法使用,最后要想使用好这个框架只能是java开发人员。也间接造成自动化组人员膨胀。


2,流程以原子操作供脚本调用,即流程写在脚本文件里,这时脚本对测试人员透明,测试人员可写脚本改脚本,测试人员对此框架掌握使用权。但一些java类中很容易实现的分支循环和字符串操作等等此时在脚本里实现难度就会加大。

后记,最近一些研究。


那有没有更好的方法呢,有,但这种方法有点脱离java范畴。在一些公司,他们的脚本语言不是xml 而是用python 和 ruby语言去写脚本,脚本写完直接运行这些脚本。这样所有问题都能解决。此时只有一个问题,python和ruby使用者在中国并不广泛。很多时候只能找java开发人员培训后去写这些脚本,此外,java开发人员愿不愿意写这些测试脚本也是一个问题。


python 和 ruby是一种解释性强的脚本语言,这也注定用它这种语言在开发一种供测试人员使用的语言也比较简单。比如DSL(领域特定语言)具体怎么创建就不说了,网上有很多实例。

当然对于java框架来说,最好的实现方法是用ruby等脚本语言创建DSL,编译后能在java虚拟机里执行就最好了,这样就可以实现在java里使用领域特定语言。比如使用xruby等等。但因为在后面谈到并发分布式执行等等,而这些java有自己的优势。所以主框架语言建议应该选用java。


好了,借花献佛,看下覃其慧所著的文章,熟悉下 DSL语言的脚本。


让测试的描述能够接近被测系统的领域语言、使测试意图得到清晰表达就是我们想要得到的效果。DSL正好能够帮我们实现。

让我们再看看之前的那段代码:

selenium.open(“/”)
selenium.type(“id=username”, “myname”)
selenium.type(“id=password”, “mypassword”)
selenium.click(“id=btnLogin”)
selenium.waitForPageToLoad(30000)
assertTrue(selenium.isTextPresent(“Welcome to our website!”))

由于使用的是通用语言,在我们这个特定的使用场景中显得过于细节化、过程化,不能清晰表达测试意图。

换成DSL,我们的测试就可以直接用验收标准的语言来描述如下:

Given I am on login page
When I provide username and password
Then I can enter the system

这样测试的内容就直观多了,还包含了一些业务信息,让我们知道这个是在测试一个登录的场景,而不是任意的输入信息,兼顾传递了业务知识的职责。至于这些DSL背后能够运行的代码,也被隐藏起来。如果是不能够阅读原来那样的测试代码的人(不管是需求分析人员还是客户甚至一些对自动化代码关注比较少的测试人员)想要加入到自动化测试活动中进行反馈,就不会被DSL背后的代码带来的“噪音”所影响。

当然,在我们的现实应用场景中,这个需求没有那么简单,我们的验收标准还会考虑不同的数据比如输入不同组合的用户名密码:

Given I am on login page
When I provide ‘david’ and ‘davidpassword’
Then I can enter the system
Given I am on login page
When I provide ‘kate’ and ‘kate_p@ssword’
Then I can enter the system

以及更多的测试数据。

那么这种情况下,仅仅是比较通俗的语言还是不够的,毕竟测试数量在那摆着。如果测试数量不能减少,维护起来仍然很麻烦。打个比方,如果系统的实现变成了每次都要输入用户名、密码和一个随机验证码,我们就需要在我们的自动化测试中修改多处,比较繁琐。因此,我们需要在可读性比较好的自然语言描述的测试上,把它的抽象层次再提高一点。

幸运的是,我们当时选择的DSL工具是cucumber,它除了提供了几个测试的描述层次:Feature,Scenario,Steps,还提供了非常好的一种组织方式—数据表。

这样,我们的这个自动化测试就可以把之前的那个登录的功能根据特性、场景总结和具体的步骤分离开来,清晰的分层,同时利用数据表我们的测试精简成一系列被重复多次但输入数据有所变化的操作过程,如下:

Feature: authentication
In order to have personalized information
I want to access my account by providing authentication information
So that the system can know who I am
Scenario Outline: login successfully
Given I am on login page
When I provide ‘<username>’ and ‘<password>’
Then I can enter the system
Examples:
|username |password |
|david |davidpass |
|kate |kate_p@ssword|

测试这下看起来就更清爽了。首先,用Feature关键字,我们把测试分类到login这个大特性下的,并对这个特性本身的业务目的进行相关描述,带进业务目标,传递业务知识;然后用Scenario关键字来提高挈领的标明我们这个测试场景中做的是测试登录成功的情况,并且把步骤都写出来;最后,我们用Examples关键字引出具体的数据表格把用到的数据都展示出来,避免我们的相同步骤因为测试数据的变化而重复若干遍造成冗余。万一碰上了需求的变化,要求同时提供用户名、密码和验证码,那我们的测试也只需要改动较少的地方就足够了。

更棒的是,用了这种数据表的方式,整个团队的协作效率提高了。对于写代码没有那么顺畅的测试人员来说,增加自动化测试也就是增加更多测试数据,填充到数据表里就可以了。




 





版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xhyspring/article/details/6994450

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签