Selenium 报表自动化测试——黑盒测试篇

Robert.jiang · 2017年11月06日 · 最后由 jasonhuang 回复于 2018年07月04日 · 2046 次阅读
本帖已被设为精华帖!

背景

公司的ERP系统有很多报表页面,每个月都会有一两个报表的功能进行修改,页面查询条件很多,
功能测试的同事尽可能的穷举出各种参数组合去查询,但由于参数组合的数量太大,人工测试的用例只能算九牛一毛,
依然有大部分漏测的场景可能发现问题,上线之后被用户投诉。

基于这个背景,引入自动化测试,旨在通过程序穷举出各种查询场景自动进行测试,并记录各场景的测试结果。

需求

从背景中收集到以下需求:

  1. 页面上有很多查询条件
  2. 穷举查询条件的组合
  3. 记录各组合的测试结果

分析

先看看页面长什么样子。

由此页面我们可以得出参数的大致列表:

<aa></aa>
<bb></bb>
<cc></cc>
<dd></dd>
<ee></ee>
……

我们把这些参数进行组合,(此处省略N字。。。组合公式自行去查阅资料)可以得到以下数量
组合数量 = 2的参数个数次幂-1
假设页面有3个参数aa,bb,cc,那么组合出来的参数为:{[aa], [bb], [cc], [aa,bb], [aa,cc], [bb,cc], [aa,bb,cc]}
如果页面有10个参数,则这些参数组合的数量为:2的10次方-1 = 1023

但上面求出来的数量仅仅只是参数的值为固定数量的组合个数,但实际上某个参数的取值可能是以下情况:

<aa>value1,value2,value3</aa>
or
<aa>value1</aa>
or
<aa>value1,value2</aa>
or
……
我们可以得知

页面条件组合出来的数量无穷大!!!😤😤😤

解决思路

从实际场景出发,了解业务初衷

我们从上面的分析可以得出一个结论,这是一个看似无法完成的任务。怎么把一个理论上来说不可能完成
的任务,变成一个可行的解决方案,就需要我们对实际的业务进行分析了。

先说自动化面临的问题:

  1. 自动化用例量庞大。假设页面有10个参数,每个参数固定一个值,则有1023个组合
  2. 自动化用例耗时长。一个完整的页面输入参数点击查询的用例大概需要5~10秒的时间,再乘以以千为单位的用例个数。。。 不说其他,单这两项就够秒杀该功能不适合做自动化的。 怎么办呢?竟然正常的路走不通,那我们就想点非常规的路子。

咱们不妨抛开业务,想想什么自动化用例才是好用例:

  1. 脚本通用,一份脚本支持所有用例执行。😍
  2. 脚本执行时间短,最好分分钟跑完。☺
  3. 覆盖更多测试场景,这个当然是越多越好。👏

回到正题,竟然这个业务本身就非常特殊,我们就没有必要去遵循自动化用例的设计原则了,直接按照我们心目中的完美用例去实现。

  1. 脚本通用。我们可以写一份脚本,然后通过循环读取参数去页面查询。
  2. 执行时间。如此庞大的用例量自然无法通过人工来写的,我们就想办法通过程序去自动生成用例。
  3. 覆盖度高。从业务角度来设置查询场景,如:参数全设置查询,参数不设置查询,等各种场景。

解决方案

最终我们得出一个比较满意的测试解决方案:

  • 使用数据驱动模式,维护参数配置文件,一份代码,所有用例都可执行
  • 测试用例通过参数组合生成,提供开关控制用例生成数量
  • 指定场景测试+随机测试(从参数组合中抓取随机数量的组合)

测试流程图

实现的功能

  • 代码与业务分离。不再需要写大量场景的自动化用例代码,仅需维护参数配置文件
  • 测试用例根据配置文件中的参数随机生成测试场景,场景数量可控(配置文件提供指定数量)
  • 2层随机机制(查询条件随机组合,查询条件中具体条件中的值随机组合),保证了场景的覆盖度
  • 2种测试方式(随机场景测试,指定场景测试)
  • 明确的测试结果。只关注点击查询后的页面结果,忽略自动化操作页面元素失败干扰测试结果(用例在页面操作失败依然往下执行)

用例代码

public class RandomParamatersQueryTest {
private WebDriver webDriver = null;
private SoftAssert softAssert = new SoftAssert();
WebManager webManager = null;
WebElementCheck webCheck = null;

@Test
public void randomParamatersQueryTest() {
String parameterXmlPath = ConfigReader.getInstance().getValue("parameterXmlPath");
String randomCaseXmlPath = ConfigReader.getInstance().getValue("randomCaseXmlPath");
String randomexcelPath = ConfigReader.getInstance().getValue("randomexcelPath");

// 使用Firefox浏览器
webDriver = WebDriverManager.GetDriver(BroswerType.FIREFOX);

webManager = new WebManager(webDriver, softAssert);
webCheck = new WebElementCheck(webDriver, softAssert);

PublicMethod common = new PublicMethod(webManager, webCheck);
SalesReport sReport = new SalesReport(webManager);

// 登录、跳转到指定报表页
common.login(ConfigReader.getInstance().getValue("loginURL"), ConfigReader.getInstance().getValue("loginEmail"),
ConfigReader.getInstance().getValue("loginPassword"));
sReport.navigateToSalesReport();
sReport.switchToFrame();

// 参数生成参数配置文件
ReportProvider.saveParamatersToCaseXML(parameterXmlPath, randomCaseXmlPath);
webManager.threadWait(3);
// 读取参数配置文件,做UI自动化测试
XmlReader xmlReader = XmlReader.getInstance(randomCaseXmlPath);

// 创建测试报告Excel
String reportName = "sales report";
ExcelOperate.getInstance().createExcel(reportName, randomexcelPath);

List<String> configIds = xmlReader.getAttributeValue("id");
for (String configId : configIds) {
try {
// 设置查询参数
sReport.setQueryParas(randomCaseXmlPath, configId);
sReport.clickSearch();

try {
// 判断错误提示框是否出现,如果出现,判定该用例执行失败
webManager.dynamicWaitAppearAndThrowException(WebElementType.XPATH,
"//div[@class='ui-dialog ui-widget ui-widget-content ui-corner-all ui-draggable ui-resizable']",
2);
webManager.threadWait(1);
String message = webCheck.getProperty(WebElementType.XPATH, "//div[@id='alert_dialog']",
CheckPropertyType.INNERTEXT);
ExcelOperate.getInstance().excelWrite2003(randomexcelPath, ExcelUpdateMode.ADD, reportName,
configId, "fail", message);
} catch (Exception e) {
// 错误框未出现,判定用例执行成功
ExcelOperate.getInstance().excelWrite2003(randomexcelPath, ExcelUpdateMode.ADD, reportName,
configId, "success", "");
} finally {
webManager.refreshBroswer();
sReport.navigateToSalesReport();
sReport.switchToFrame();
}

} catch (Exception e) {
// 任何错误都不抛出,保证用例的循环执行
}
}

common.logout();
}
}

两种测试方式

随机测试与指定测试的区别在于用例配置文件一个是根据参数随机生成,一个是用户自己指定场景的参数

随机测试

参数文件

<?xml version="1.0" encoding="UTF-8"?>
<Testcases xmlns="testcase">
<parameters>
<grouptype>1,2,3</grouptype>
<market>504</market>
<ps>2877</ps>
<orggroupby>1,2,3,4,5</orggroupby>
<channel>287</channel>
<channelcountry></channelcountry>
<productline></productline>
<pm></pm>
<cm>2877</cm>
<salesdate>2017-02-01,2017-02-02</salesdate>
<country>US,UK,FR</country>
<curno>USD</curno>
<shippingperiod>1</shippingperiod>
<model>TT-BH07</model>
<sku>53-10007-001</sku>
</parameters>
</Testcases>

生成的随机测试参数配置文件每次都会改变,这样保证了用例的场景覆盖度(生成的case数量可控,在配置文件中设置即可)

<?xml version="1.0" encoding="UTF-8"?>
<Testcases xmlns="testcase">
<Testcase xmlns="" id="case1">
<country>US,UK,FR</country>
<channel>287</channel>
<curno>USD</curno>
<shippingperiod>1</shippingperiod>
<grouptype>1,2,3</grouptype>
<salesdatefrom>2017-02-01</salesdatefrom>
<salesdateto>2017-02-02</salesdateto>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case2">
<cm>2877</cm>
<shippingperiod>1</shippingperiod>
</Testcase>
<Testcase xmlns="" id="case3">
<country>US,UK,FR</country>
<ps>2877</ps>
<cm>2877</cm>
<curno>USD</curno>
<market>504</market>
<orggroupby>1,2,3,4,5</orggroupby>
<salesdatefrom>2017-02-01</salesdatefrom>
<salesdateto>2017-02-02</salesdateto>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case4">
<country>US,UK,FR</country>
<channel>287</channel>
<cm>2877</cm>
<market>504</market>
<grouptype>1,2,3</grouptype>
<orggroupby>1,2,3,4,5</orggroupby>
<salesdatefrom>2017-02-01</salesdatefrom>
<salesdateto>2017-02-02</salesdateto>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case5">
<shippingperiod>1</shippingperiod>
<market>504</market>
<orggroupby>1,2,3,4,5</orggroupby>
<model>TT-BH07</model>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case6">
<country>US,UK,FR</country>
<channel>287</channel>
<curno>USD</curno>
<shippingperiod>1</shippingperiod>
<model>TT-BH07</model>
</Testcase>
<Testcase xmlns="" id="case7">
<country>US,UK,FR</country>
<ps>2877</ps>
<channel>287</channel>
<curno>USD</curno>
<market>504</market>
<orggroupby>1,2,3,4,5</orggroupby>
<model>TT-BH07</model>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case8">
<cm>2877</cm>
<curno>USD</curno>
<market>504</market>
<grouptype>1,2,3</grouptype>
<salesdatefrom>2017-02-01</salesdatefrom>
<salesdateto>2017-02-02</salesdateto>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case9">
<country>US,UK,FR</country>
<ps>2877</ps>
<curno>USD</curno>
<shippingperiod>1</shippingperiod>
<market>504</market>
<grouptype>1,2,3</grouptype>
<model>TT-BH07</model>
<sku>53-10007-001</sku>
</Testcase>
<Testcase xmlns="" id="case10">
<ps>2877</ps>
<curno>USD</curno>
<market>504</market>
<model>TT-BH07</model>
</Testcase>
</Testcases>
指定测试

参数文件

<?xml version="1.0" encoding="UTF-8"?>
<Testcases xmlns="testcase">
<Testcase id="allparamaters">
<grouptype>1,2,3,4,5,6,7,8,9,53,54,55</grouptype>
<market>504</market>
<ps>3817,3782,2877,1620,3619,3661</ps>
<orglist>1,2,3,4,5</orglist>
<channel>12</channel>
<channelcountry></channelcountry>
<productline></productline>
<pm>3660,3619,1620,2887,3813</pm>
<cm>3661,2877,3782,1620,3817</cm>
<salesdatefrom>2017-02-01</salesdatefrom>
<salesdateto>2017-02-02</salesdateto>
<country>CA,CN,DE,ES,FR,HK,IT,JP,UK,US</country>
<curno></curno>
<shippingperiod></shippingperiod>
<model></model>
<sku></sku>
</Testcase>
<Testcase id="noneparamaters">
<grouptype></grouptype>
<market></market>
<ps></ps>
<orglist></orglist>
<channel></channel>
<channelcountry></channelcountry>
<productline></productline>
<pm></pm>
<cm></cm>
<salesdatefrom></salesdatefrom>
<salesdateto></salesdateto>
<country></country>
<curno></curno>
<shippingperiod></shippingperiod>
<model></model>
<sku></sku>
</Testcase>
</Testcases>

总结

通过指定测试场景与随机测试场景,达到报表黑盒自动化测试的目的。

结尾

下一篇将分享报表数据初始化的流程,对数据进行白盒自动化测试。

共收到 29 条回复 时间 点赞

略显尴尬,做UI自动化的同行,难道不做查询的黑盒测试?

报表查询确实很难穷尽,但用户又会用各种你预想不到的组合进行查询。这个思路挺不错的。

chenhengjie123 将本帖设为了精华贴 11月08日 07:24

all-pairwise难道已经被人忘记了么
靠随机,还说能保证覆盖率?

fudax 回复

有两种测试机制的,指定测试和随机测试。
基于业务背景:
我们会把典型场景的参数配置在指定配置文件中。这一块执行完后,我们在业务上认可其可达到90%的覆盖度。
再通过参数组合出场景,生成测试场景的参数用例。理论上讲,只要有足够的资源,不用随机抽取数量,直接全跑,肯定可以达到100%覆盖的。
但实际情况是这些耗时的用例,性价比并不高,收益也很低,而且需要非常大的成本。
所以我们提供一个可设置的随机数量阀值,找到一个平衡的随机数值,抽取用例进行测试。每次执行随机测试,都用这部分随机用例去提高剩下10%的覆盖度

Robert.jiang 回复

这个看你们喜好吧,不争辩,建议你还是去看看正交覆盖法、all-pairwise算法,然后回头再来审视自己的做法

fudax 回复

好的,多谢提供宝贵的建议

为什么一定要弄UI,接口不行吗?

正交覆盖法也不少用例,然后根据业务需求一条条砍。。。。砍到剩100来条,发现,其实嘛。。。手工也行。。。懒是一种病😂

jamesparagon 回复

因为懒,才能体现自动化的价值,😂

tester6636865 回复

接口测试也有做的

UI报表自动化怎么造数据?数据源怎么来的,UI上操作吗,我去年做的方案是接口自动化来实现

数据初始化现在正在实现中,过段时间会分享上来。
我们这套系统报表的数据是从第三方系统获取过来的。

挺好的,不过我倒是赞成 @fudax 讲的,case组合用正交法 或 all-pairwise 生成case,个人更加倾向正交法;其次,我觉得这块主要测试的是数据,没必要使用UI,直接采用 字段case组合 + 接口请求+ 监测sql执行语句 比对 可能效率更高

hu_qingen 回复

嗯,其实正交覆盖法就是复杂度或者说深度为2的pairwise的应用啦

我现在用allpair生成测试用例,但是根据业务条件筛选太麻烦了,费时费力,想用python做这个事情,又没有什么头绪

jamesparagon 回复

无论使用那种方法,只要是穷举,都会有很庞大的数量。用正交或者结对可能数量会少一些,但1000和400的区别不大,最终也是要从这1000或者400中随机取一部分用例出来执行,硬伤啊!

同意正交

@Robert.jiang 转一下微信公众号上一位热心同学的留言:

我也想学习下怎么将报表自动化测试,请问一下您这个代码,我可以将我的条件,按照这个参数文件来编写代码即可吗?请问如果刚拿到一个报表,怎么样从头开始搭建呢?求指教

请问需要添加哪些类啊,求完整代码,谢谢

我还在想前面读取数据库数据,到这里循环查询,怎么去执行?

chenhengjie123 回复

@chenhengjie123 回热心网友。

我可以将我的条件,按照这个参数文件来编写代码即可吗?

  • 可以。你需要把你的条件参数配置在参数文件中,然后调用随机生成case的功能即可获取你需要的测试用例参数。

请问如果刚拿到一个报表,怎么样从头开始搭建呢?

  • 确认测试范围(选取测试条件与测试条件的值)
  • 参数配置文件。将选取的测试条件配置在参数文件中

我还在想前面读取数据库数据,到这里循环查询

  • 你可以在测试之前,将你的数据读取出来,然后写入到参数配置文件中即可

需要添加哪些类?

  • 请参考文章中测试用例代码部分,在调用具体的功能处(大概就是你说的类吧)有详细的注释

循环查询,怎么执行?

  • 请参考文章中测试流程图

设计思路:从参数配置文件中读取参数,生成参数组合的case,读取case中参数值,通过selenium、js、jQuery等手段将该值设置到UI对应的条件中进行查询。如此,一份代码即可执行所有场景的测试用例。

遇到过这样多的参数,我用接口测试的方式……基于pict生成组合用例,每个用例用多线程跑的

感谢楼主提供的思路,我现在也遇到报表页面的测试,但我不是很精通代码,想问下楼主,您这个方法(对参数文件的维护,实现一份脚本跑完测试用例)能否在loadrunner实现?我之前都是使用接口进行测试的,萌新测试,求指教

alex.pan 回复

应该不行吧,这个是java+selenium,纯代码代码实现的。
不是很清楚loadrunner

比较关心楼主判断用例是否通过的逻辑,代码中说是判断是否有错误提示框,可以具体点吗?有对返回的数据本身做验证吗?报表有正常返回但是数据却是错误的时候,不知道楼主这块有没有涉及呢?期待回复!谢谢!

leahyezi 回复

文中黑盒测试的重点是:对页面的条件进行组合后单击查询按钮。这是一个大数据量的操作,因此不对返回数据做校验,只看本次组合的条件在页面是否可正常使用。如果查询失败,系统会有弹出框提示失败原因,这个应该很好理解的。我们抓取这个框是否在一定的时间内出现,如果出现则判定本次查询失败,记录用例结果。

涉及到数据校验,需要做指定的校验处理,不适合随机性的测试。比如使用什么条件,查询了多少条数据出来,通过xpath、css、id、name等等方式得到页面查询出来的值,然后再对其进行验证。

Robert.jiang 回复

谢谢回复!其实我这边的业务刚好是对数据校验这块问题比较注重,你有什么样比较通用的接口测试方法吗?
先简单介绍下我的情况,需求那边有很频繁的报表提测任务,而且经常是新增报表,数据源来源于比较复杂的业务方数据表,所以做统计的过程经常是很多表进行关联各种条件查询,如果每次都检查开发的sql取数逻辑,一个报表就需要花费比较多的测试时间了。

leahyezi 回复

涉及大量业务数据的校验,不适合做黑盒的测试。
另外从你的描述来看,校验的逻辑也会非常多,UI自动化需要写太多校验代码,事倍功半。
你可以考虑从接口测试下手,接口测试在校验方面很简单,直接对response的结果做对比就行。
接口测试你可以考虑postman,现在网上也有很多开源的接口测试平台,都可以满足你的需求。如果不想用别人的工具,也可以自己写框架,python的话可以使用request库,java的话,用httpclient类。都可以做接口测试的

你是怎么用接口实现的?能否给个参考,目前公司正打算对report进行自动化。不过感觉进行起来比较困难

要是有python示例就更好了0.0

仅楼主可见
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册