测试同学一般在测试网页应用的时候,都会按着既定的用例描述自行操作一遍,来验证输出结果是否符合预期,这相信也是大部分公司招聘测试员的工作范围之一。
这种作业模式,在上线频率不高的前提下,还是可以满足要求的。完整执行一次回归测试,覆盖系统所有功能,实际上也是比较耗费时间的。
自动化测试固然可以解决测试效率问题,但测试同学对自动化的实施通常存在以下疑问:
这些疑问的核心是:如何很好的维护测试代码
这里「好」的意思是:
这些问题,其实和测试并没有直接关系,这是一般编程上的抽象要求,和可复用性的要求,只是把相同的概念用在测试领域上而已。
以下介绍一个方案:Cucumber + Selenium WebDriver
官网:https://cucumber.io
简单说,用Cucumber为了简化编写测试程序,让我们写测试程序就如同写文章一样。例如:
# language: zh-CN
功能: 企业创建套餐,应付计算
场景: 创建基础服务套餐(免费试用)
假如存在企业为:"CA", 密码为:"123456789", 账号为:"13578819805"
假如创建产品编号为:"P001",单价为:"0"
当企业:"CA"购买清单如下:
| 产品编码 |数量|
| P001 |1 |
那么应付金额为:"0.0"
当然,文字的背后,需要写一小段代码支撑该段文字:如下面(使用Java8的Lamba语法)
public class CorporationStepdefs implements Zh_cn {
public CorporationStepdefs() {
假如("存在企业为:{string}, 密码为:{string}, 账号为:{string}", (String corpId, String password, String mobile) -> {
// TODO 若企业不存在,则创建企业
});
}
如何使用Cucumber,就不在这里展开说了,需要进一步了解的请参考官网说明。
官网:https://www.selenium.dev/documentation/en/webdriver/
简单说,Selenium提供浏览器自动化的标准接口,用同样的命令集就可以驱动不同的浏览器,进行程序控制的点击,录入,查询界面内容等工作。
看到Selenium的接口,控制的都是界面级别的元素操作。如:
WebDriver driver = new ChromeDriver();
driver.get("https://www.baidu.com");
WebElement logo = driver.findElement(By.id("s_lg_img"));
logo.click();
看到这样细致的操作,你可能会有两个反应:
所以我们需要结合Cucumber来做整个编写自动化测试的方案。
https://github.com/kmtong/cucumber-selenium-sample
此例子尝试用两套方式实现同一个测试功能:百度搜索。
这里描述了两个场景:
两者差别在于操作粒度:一个用元素操作,另一个只描述业务层。
针对元素级别,我们可以根据需要把Selenium的操作变成一般语言,让测试可以灵活组合达到测试目的。
至于业务级别的定义,好处显然是可以用很少的语言来描述整个业务(封装成一句简单的语句),同时可以清楚表达操作步骤和因果关系。当页面改变了,如果业务没变动,只需要更改某一句的实现方式即可。
让我们简单看看各层的实现:
/**
* 低阶浏览器操作定义
*
* @author kmtong
*/
public class BrowserStepdefs implements Zh_cn {
public BrowserStepdefs(SampleTestContext context) {
当("使用谷歌浏览器", () -> {
context.cleanup();
context.setWebDriver(new ChromeDriver());
context.getWebDriver().manage().window().maximize();
context.getWebDriver().manage().timeouts().implicitlyWait(3, TimeUnit.SECONDS);
});
当("访问网站{string}", (String string) -> {
context.getWebDriver().get(string);
});
当("点击{string}按钮", (String string) -> {
context.getWebDriver().findElement(By.xpath("//input[@value='" + string + "']")).click();
});
当("填写{string}到{string}", (String value, String field) -> {
context.getWebDriver().findElement(By.name(field)).sendKeys(value);
});
那么("页面显示内容含有{string}", (String string) -> {
String bodyText = context.getWebDriver().findElement(By.tagName("body")).getText();
assertTrue("Body找不到内容: " + string, bodyText.contains(string));
});
当("等待{int}秒后", (Integer timeout) -> {
Thread.sleep(timeout * 1000L);
});
// cleanup
After(() -> {
context.cleanup();
});
}
}
/**
* 高阶操作定义
*
* @author kmtong
*/
public class BaiduSearchStepdefs implements Zh_cn {
public BaiduSearchStepdefs(SampleTestContext context) {
当("使用百度搜索{string}", (String search) -> {
context.getWebDriver().get("https://www.baidu.com");
context.getWebDriver().findElement(By.name("wd")).sendKeys(search);
context.getWebDriver().findElement(By.xpath("//input[@value='百度一下']")).click();
Thread.sleep(500L);
});
}
}
两组实现相对简单易懂,组合的语言也相对流畅直白。
这里还用上了picocontainer,测试时候需要的状态,通过SampleTestContext共享了。目前非常简单,只有WebDriver一个对象,而当测试场景变复杂时,测试的状态变得非常重要了,最可能共享的状态有:登入状态,某步骤的执行结果等。当我们用上跨测试对象(Stepdefs)时,这个技巧变得尤其重要了。
回到最初测试同学们都关注的问题:
这个方案很好的解决了以上问题,当然界面改动在所难免,会导致测试代码改动,但起码改动范围可控,语言文字逻辑层基本可以保持不变。
大家不妨试试这种自动化测试方法。