用java构建企业级自动化框架(首篇-制定测试者使用语言3)

这个是我后来写的一本书,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关键字引出具体的数据表格把用到的数据都展示出来,避免我们的相同步骤因为测试数据的变化而重复若干遍造成冗余。万一碰上了需求的变化,要求同时提供用户名、密码和验证码,那我们的测试也只需要改动较少的地方就足够了。

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




 





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值