初探网页应用的自动化测试:Cucumber + Selenium

前言

测试同学一般在测试网页应用的时候,都会按着既定的用例描述自行操作一遍,来验证输出结果是否符合预期,这相信也是大部分公司招聘测试员的工作范围之一。

这种作业模式,在上线频率不高的前提下,还是可以满足要求的。完整执行一次回归测试,覆盖系统所有功能,实际上也是比较耗费时间的。

自动化测试固然可以解决测试效率问题,但测试同学对自动化的实施通常存在以下疑问:

  1. 浏览器的自动化访问脚本,很容易因为界面的改动,导致以前的测试脚本失效,维护成本高
  2. 编写测试脚本,由于是界面录入和点击级别的描述,工作量巨大

这些疑问的核心是:如何很好的维护测试代码

这里「好」的意思是:

  1. 如何分离物理层(HTML)和逻辑层(业务逻辑),让逻辑层不会因为底层(物理层)的变化,而需要重新写一遍
  2. 如何提升操作和描述层次,让我们可以描述点击元素级别的操作(物理层操作),也可以描述高层次的操作(业务操作)。

这些问题,其实和测试并没有直接关系,这是一般编程上的抽象要求,和可复用性的要求,只是把相同的概念用在测试领域上而已。

以下介绍一个方案:Cucumber + Selenium WebDriver

什么是Cucumber

官网: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,就不在这里展开说了,需要进一步了解的请参考官网说明。

什么是Selenium WebDriver

官网: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();

看到这样细致的操作,你可能会有两个反应:

  1. 这样精细的操作,表达能力很强,可以控制得非常准确
  2. 这样细致的指令,要完整的写好一个自动化测试,工作量巨大

所以我们需要结合Cucumber来做整个编写自动化测试的方案。

例子

https://github.com/kmtong/cucumber-selenium-sample

此例子尝试用两套方式实现同一个测试功能:百度搜索。

初探网页应用的自动化测试:Cucumber + Selenium_第1张图片

这里描述了两个场景:

  1. 使用元素级别进行百度查询
  2. 使用高阶百度查询

两者差别在于操作粒度:一个用元素操作,另一个只描述业务层。

针对元素级别,我们可以根据需要把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)时,这个技巧变得尤其重要了。

回到最初测试同学们都关注的问题:

  1. 浏览器的自动化访问脚本,很容易因为界面的改动,导致以前的测试脚本失效,维护成本高
  2. 编写测试脚本,由于是界面录入和点击级别的描述,工作量巨大

这个方案很好的解决了以上问题,当然界面改动在所难免,会导致测试代码改动,但起码改动范围可控,语言文字逻辑层基本可以保持不变。

大家不妨试试这种自动化测试方法。

你可能感兴趣的:(笔记,领域设计,杂谈,selenium,软件测试)