
乐高-美团界面自动化测试实践
【keywords start】智能化测评【keywords end】 1. 概述 1.1 接口自动化概述
众所周知,接口自动化测试具有以下特点:
如何完成一个接口自动化测试项目?
我认为,一个“好的”自动化测试项目需要从“时间”、“人力”和“利润”三个方面做出良好的“权衡”。
仅仅因为被测系统发生了一些变化,花费了几个小时的自动化脚本就无法执行。 同时,我们还要看到“利”。 我们不能因为总是希望看到 100% 的成功而少做或不做验证。 但如果验证太多,维护成本肯定会增加,每天可能需要大量的维护。
因此,平衡这三个方面并不容易。 经常看到做自动化的学生最终本末倒置。
1.2 提高投资回报率
要提高ROI(投资回报率),必须从两个方面入手:
降低投入成本。 增加使用量。 以“降低投入成本”为目标
我们需要做的是:
以“增加使用量”为目标
我们需要做的是:
因此,我开发了乐高接口测试平台来实现我的一些自动化测试想法。 首先,简单浏览一下网站,了解一下它是什么样的工具。
1.3 乐高的组成
乐高接口测试方案由两部分组成,一是你刚才看到的“网站”,二是“脚本”。
我们先来介绍一下“脚本设计”部分。
2. 脚本设计 2.1 乐高的做法
乐高界面自动化测试脚本部分采用了非常常见的Jenkins+TestNG结构。
相信看到这样的模型并不陌生,因为很多测试都是这样组成的。
在MySQL数据库中存储自动化测试用例已经成为一种更常见的“数据驱动”方法。
许多团队也使用这种结构来自动化界面。 如果继续使用,以后“晋升”时学习和迁移的成本就会很低。
2.2 测试脚本
首先我们简单看一下当前的脚本代码:
public class TestPigeon {
String sql;
int team_id = -1;
@Parameters({"sql", "team_id"})
@BeforeClass()
public void beforeClass(String sql, int team_id) {
this.sql = sql;
this.team_id = team_id;
ResultRecorder.cleanInfo();
}
/**
* XML中的SQL决定了执行什么用例, 执行多少条用例, SQL的搜索结果为需要测试的测试用例
*/
@DataProvider(name = "testData")
private Iterator<object[]> getData() throws SQLException, ClassNotFoundException {
return new DataProvider_forDB(TestConfig.DB_IP, TestConfig.DB_PORT,
TestConfig.DB_BASE_NAME,TestConfig.DB_USERNAME, TestConfig.DB_PASSWORD, sql);
}
@Test(dataProvider = "testData")
public void test(Map<string, string=""> data) {
new ExecPigeonTest().execTestCase(data, false);
}
@AfterMethod
public void afterMethod(ITestResult result, Object[] objs) {...}
@AfterClass
public void consoleLog() {...}
}
</string,></object[]>
有一种做法我从来不提倡,就是直接在Java文件中编写测试用例。 这样做会带来很多问题:修改测试用例需要修改大量代码; 把代码交给其他同学也不方便,因为每个人都有自己的编码风格和用例设计风格。 这样一来,交接最终就会成为下一次的交接。 同学们全部推翻重写; 如果测试平台发生变化,用例数据无法迁移,只能手动一一重新录入。
因此,将“测试数据”和“脚本”分开是非常有必要的。
互联网上的许多示例都是使用 Excel 进行数据驱动的。 为什么我改用 MySQL 而不是 Excel?
在公司,我们的脚本和代码都提交到公司的Git代码仓库中。 如果你使用Excel…每天频繁修改测试用例显然很不方便。 使用MySQL数据库就不存在这样的麻烦。 由于数据和脚本分离,只需要修改数据即可。 该脚本每次都会读取数据库中最新的用例数据进行测试。 同时也可以防止操作代码时的一些误操作。
这里再贴一段我自己写的DataProvider_forDB方法,方便其他同学在自己的脚本中使用:
import java.sql.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* 数据源 数据库
*
* @author yongda.chen
*/
public class DataProvider_forDB implements Iterator<object[]> {
ResultSet rs;
ResultSetMetaData rd;
public DataProvider_forDB(String ip, String port, String baseName,
String userName, String password, String sql) throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
String url = String.format("jdbc:mysql://%s:%s/%s", ip, port, baseName);
Connection conn = DriverManager.getConnection(url, userName, password);
Statement createStatement = conn.createStatement();
rs = createStatement.executeQuery(sql);
rd = rs.getMetaData();
}
@Override
public boolean hasNext() {
boolean flag = false;
try {
flag = rs.next();
} catch (SQLException e) {
e.printStackTrace();
}
return flag;
}
@Override
public Object[] next() {
Map<string, string=""> data = new HashMap<string, string="">();
try {
for (int i = 1; i <= rd.getColumnCount(); i++) {
data.put(rd.getColumnName(i), rs.getString(i));
}
} catch (SQLException e) {
e.printStackTrace();
}
Object r[] = new Object[1];
r[0] = data;
return r;
}
@Override
public void remove() {
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
</string,></string,></object[]>
2.3 配置文件
上图中提到了“配置文件”。 我们简单看一下这个XML配置文件的脚本:
我们参考上图解释一下配置文件:
这样做有什么好处?
使用SQL的最大优点是灵活性
比如上面的例子,会在数据库中查询下面56个测试用例,然后这个标签就会对这56个测试用例进行一一测试。
当有多个标签时,可以分组显示
使用多个标签来区分用例的最大好处是,在最终的报表中也可以达到分组展示的效果。
报表更美观、更丰富
由于使用ReportNG来打印报告,因此报告显示比TestNG自带的报告更加美观,并且显示风格可以自定义。 点击它可以查看详细的执行过程。
如果有执行失败的用例,通常会在顶部首先显示报告错误的用例。
支持多个团队
当两个团队开始使用时,为了方便维护,基本部分被抽离出来。 各团队的脚本都依赖于这个Base包,并且Base包版本设置为“SNAPSHOT版本”。 使用“SNAPSHOT版本”的好处是,当我以后更新乐高时,各个业务组都可以及时更新,而不需要对脚本进行任何更改。
当更多的团队开始使用它时,它会更直观地看起来像这样:
每个团队的脚本依赖于我的Base包,所以最终每个业务团队的脚本是这样的:
正如你所看到的,使用乐高后:
您还可以使用 Jenkins 来实施预定的构建测试。
由于所有测试用例都在数据库中,所以这个脚本基本上不需要修改,减少了大量的脚本代码。
有同学想问,有时候写接口测试用例不仅仅是请求接口。 可能还需要写一些数据库操作。 您可能需要自己编写一些方法来获取一些参数。 没有代码怎么处理? 呢绒?
让我们进入“用例设计”,我将介绍我如何通过统一的用例模板来解决这些问题。
3. 用例设计 3.1 一些想法
我在做界面自动化设计的时候,会考虑以下几点:通用性、可验证性、健壮性、易用性。
通用模板可用于统计和可扩展验证
在编写自动化脚本时,每个人都希望“一丝不苟”,然后“写很多”检查点; 但当“检查点”过多时,执行就会因为多种原因而失败。 因此,我们的设计需要在保证足够的检查点的同时,尽可能减少误报。
最大限度地减少误报并保持稳健
在测试执行过程中,难免会报错。 执行失败可能的原因有很多,简单分为4类:
对于上述情况:
IP表项变更灵活,可重复使用
通过这些手段,提高了测试用例的健壮性,使每个自动化测试用例都能很好地完成测试任务,真正发挥出测试用例的价值。
易用性和减少代码操作需要3.2乐高界面自动化测试用例
话虽如此,我们还是来看看乐高界面测试用例是什么样子的。
一个乐高自动用例的执行顺序大致如下图:
简单区分一下各个部分,就可以看到:
上图中提到了两个术语:
下面对这两个术语进行简单介绍。
3.3 参数化
例如,请求所需的参数。
{
"sync": false,
"cityId": 1,
"source": 0,
"userId": 1234,
"productId": 00004321
}
本例中有一个参数“productId”:00004321,在测试环境中,00004321表单很可能发生了一些状态变化,甚至表单已被删除,导致接口请求失败。 这时候就非常适合用“productId”:00004321来进行参数化,比如这样写:
{
"sync": false,
"cityId": 1,
"source": 0,
"userId": 1234,
"productId": ${myProductId}
}
那么“参数化”的简单理解就是:
通过一些操作,将测试用例中的一个“值”替换为“替换字符”
${myProductId}的值可以通过配置获取:
SQL获取现有测试用例的“参数化”实例
我们来看一个“参数化”的例子:
(1)首先我们在参数化维护页面新建一个参数化shopdealid。
通过配置我们可以看到该参数的值为执行一条SQL后执行结果中DealID字段的值。
(2) 在用例中,将需要此表单编号的地方替换为${shopdealid}。
写测试用例的时候可以看一下这张放大图。 这里的ProductID的值并不是硬编码的固定形式的数字,而是刚刚配置的参数化数据。
(3)执行结果中${shopdealid}变为从实时查询数据库中获取的真实单号。
从结果中可以看到,我们的参数已经被替换为有效值,而这个值是从我们刚刚配置的SQL实时查询中获取的。
“参数化”场景
多个测试用例使用相同的参数进行测试
例如,如果50个测试用例都使用相同的ID作为参数进行测试,那么我们需要更改ID。
没有参数化:
测试数据过期导致测试用例执行失败
例如,某个用例参数需要传入Token,但是Token会因为时间问题而过期,此时用例就会失败。
没有参数化:
当参数化时:
从数据库中获取有效的测试数据
DealId需要作为参数传入。 如果参数是硬编码的,如果DealId被修改导致失败,测试用例将无法执行。
不使用乐高时:
乐高上的解决方案: – 使用参数化实时获取sql结果,查询符合条件的dealId来实现。 – 使用参数化,调用写好的“生成订单”接口用例实现,并使用订单号来实现。 – 动作前和动作后,插入一条满足条件的数据。
3.4 行动前和行动后
“行动前和行动后”的概念更容易理解:
在接口请求之前(或之后),执行一些操作
目前支持 6 种类型的前置和后置操作:
现有测试用例执行MQ消息发送HTTP请求等待时间自定义Java方法
这里的SQL也支持Select操作。 其实这里做了一些小设计,将查询的所有结果都放到这个全局Map中。
例如,查询 SQL 语句会产生如下表所示的结果:
编号 名称 年龄 数量:–: :–: :–: :–: :–:0 张三 18 18 11221 11221 李斯 30 3344
那么我们可以使用下面左边的表达式来得到相应的结果:
也提供:
这种设计更有利于在设计用例时提供数据准备操作。
“行动前和行动后”的示例
(1)首先我们在前后台维护页面新建一个action,获取库存限额的未售出的团单。
此配置还可以支持在线调试。 调试时可以看到可以使用的参数化:
(2)在测试用例中的前动作中,添加未售出的团单,获取库存上限。
这样就可以用${pre.ProductID}来替换整个测试用例中的原始数据信息。
(3)最后请求接口并返回执行成功。
问答
Q:如果我们也获取3个参数,那么使用3个“参数化Select操作”和使用1个“预操作Select操作”有什么区别?
A:区别在于执行时间。 例如我们查询最新有效团单的“订单号”、“订购人”、“手机号”三个字段。 使用三个“参数化Select操作”:也许在执行${order number}时,得到的订单号是“10001”,但是在执行${order person}时,可能有人下了另一个订单。 下单时,下单的人可能已经变成了“10002”的“李四”,而不是“10001”的“张三”。 最终可能存在“订单号”、“订单人”、“手机号”这三个字段的数据,并不是同一行的数据。 使用“前置Select操作”可以避免上述问题,因为所有字段的数据都是一次性查询出来的,所以不会出现错位的情况。
问:“参数化选择操作”和“预操作选择操作”等不同的取值时机有什么好处?
A:由于“预动作”必须在接口请求之前执行,所以“参数化”只有在使用时才必须执行。 因此,在检查点中,如果要验证某个数据库字段在接口调用后是否发生变化,可以使用“预操作”和“参数化”同时查询该字段,然后进行比较。 如果有任何不一致,就意味着发生了变化。 因此,根据使用场景选择合适的参数化方法非常重要。 选择正确的方法可以大大提高测试用例的测试数据的稳健性。
3.5 执行各部分
回到一开始的流程图,我们可以一一看一下执行过程。
测试已启动
测试启动基本采用Jenkins,稳定、成熟、简单,有公司工具组支持。 它还支持从乐高的网页执行。
数据/环境准备
使用@DataProvider从DB数据库读取测试用例并一一执行进行测试。
测试执行
在测试用例正式执行之前,会进行一波参数替换动作。 调用该接口后,还会进行参数替换动作。
参数替换后,会执行前置动作,调用接口后执行后测试动作,最后执行后置动作。
接口请求部分就不多说了。 就是通过接口请求的参数去请求对应的接口,并得到返回结果。
这里的话是为了方便和通用,所以要求返回的结果是String类型。 这样做的最大好处是。 例如,我现在有一个新的接口类型需要连接。 那么你只需要编写一个方法来请求这个接口并得到String类型的返回结果,就可以快速将新的接口类型连接到乐高测试平台进行接口测试。
检查点
检查点部分是自动化测试用例的本质。 一个自动化测试用例能否真正发挥它的测试功能,取决于QA是否做好了这个测试用例的检查点的编写。 在乐高平台上,我目前有 6 种不同类型的检查点。
Not NULL 检查点 包含检查点 不包含检查点 数据库参数 检查点 JsonPath 检查点
JsonPath的基本写法是:{JsonPath语法}==value
JsonPath 的语法与 XPath 类似。 他们都是根据path方法来找值的。 这里主要检查返回JSON数据的结果。
具体JsonPath语法可以参考:
说完了“JsonPath语法”,现在我们来说说“JsonPath检查点语法”。 “JsonPath检查点语法”是我自己的想法,主要用于验证以下数据类型:
(1) String类型结果检查
例如:
(2)数值验证
例如:
(三)清单结果检查
例如:
(4)时间类型处理
将时间戳转换为日期和时间字符串:.todate
例如:
当JsonPath返回的结果是列表形式时,检查点,检查点,等号左边的期望值验证效果
{$.value}==“好”
[‘好’、‘好’、‘坏’、‘好’]
“好的”
作为四个检查点,列表中的每个对象都将根据“期望值”一一进行测试。 每个比较都是一个独立的检查点。
{$.value}==[“好”]
[‘好’、‘好’、‘坏’、‘好’]
[“好的”]
作为一个检查点,整体做一个全面的比较。
{$.value}==[‘a’, ‘b’]
[[‘a’, ‘b’],[‘a’, ‘b’],[‘a’, ‘b’, ‘c’]]
[‘a’,’b’]
作为3个检查点,原理与1相同,将列表中的数据与期望值进行比较。
除此之外,还有很多玩法
JsonPath 中的检查支持“参数化”和“前后操作”,因此您会看到许多诸如:
{$.param}=’${param}’ && {$.param}==${pre.param}
检查点是这样的:
“参数化”和“动作前和动作后”也支持递归配置。 这些都是为了让接口自动化测试用例的编写更加灵活、易用。
检测结果
使用 ReportNG 打印精美的报告。
报表会自定义一些显示方式,比如高亮显示。 使用ReportNG前只需添加以下语句即可支持“输出转义”并使用HTML标签自定义输出样式。
System.setProperty("org.uncommons.reportng.escape-output", "false");
后期优化
使用Jenkins执行时,会定期通过Jenkins API和Base包中的一些方法获取测试结果,存储在数据库中,并用于生成统计图表。
4. 网站功能 4.1 网站开发
既然打算做一个工具平台,那么方方面面都要设计。 不幸的是,由于缺乏人力和时间,我只能在下班时间进行开发。 可以看作承担了乐高平台的产品、后端开发、前端开发、运维、测试等各种角色。
Jenkins+TestNG+ReportNG+我自己开发的基本接口自动化测试基础jar包基本上不会太难。 但对于网站来说,在来美团之前,我从来没有开发过这样的工具平台。 这是我的第一个带有网络界面的工具。 我当时正在谷歌工作,但没想到很快就构建了一个简单的版本。
采用Servlet+Jsp进行开发。 前端框架使用Bootstrap,前端数据使用jstl,数据库使用MySQL,服务器使用公司的Beta环境Docker虚拟机。 该域名为申请的公司内网域名,开放于北京、上海。 网络访问权限。
功能基本要满足。 界面虽然不能惊艳,但也不能丑陋。 功能还算满意,但界面看起来像是20世纪80年代的。 我会厌恶使用它,所以我仍然更喜欢它的界面。 花了一些时间来调整和设计。 一旦习惯了,速度会快得多。
4.2 总体构成
Lego目前由五个不同的项目组成,分别是“测试脚本”、“Lego-web page项目”、“执行接口测试的基础包”、“小工具集合Lego-kit”和“lego-job”,可以看到依赖关系通过上图可以了解各个项目之间的关系。
各个项目的详细功能如下:
简单来说就是网站部分和脚本分离,中间的链接就是数据库。 因此,没有网站的情况下脚本执行是没有问题的; 同样,网站的运行与脚本无关。
4.3 使用-日常维护步骤1
您每天上班时都会收到这样一封测试电子邮件。 通过邮件可以了解昨晚的执行情况。 如果报告错误,可以点击“详细报告链接”跳转到在线报告。
第2步
在当前报告中,您可以直接看到执行错误信息,然后点击“乐高维护门户”即可跳转到乐高站点进行用例维护。
步骤3
跳转到站点后,可以直接显示测试用例的所有信息。 定位、维护、保存、维护用例,可以点击“执行”查看维护后的执行结果。 维护完毕后,只需“保存”即可。
只需3步,1~2分钟即可完成失败用例的定位、调试和维护。
4.4 用例编辑
通过该页面,我们可以执行一个测试用例:
4.5 在线调试
lego-web项目也使用base来执行用例,因此执行结果和打印与脚本执行一致。
4.6 用例生成工具
为了更方便的编写用例,针对部分接口开发了一个一键批量生成用例的小工具。
4.7 执行结果分析
通过Jenkins接口和Base包中的基本Test方法,将结果收集到数据库中,方便各组分析测试结果。
这是每日执行后的成功率图表:
还可以按月进行统计,生成统计图表,帮助各团队收集、整理月报数据。
4.8 故障原因追踪
通过直观地显示测试结果的图表,您将需要跟踪失败的原因。
因此,在成功率数据的右侧,会有一个跟踪失败原因的条目,也可以直观地看到哪些失败原因没有被跟踪。 点击后可以记录失败的原因。
最后会生成一个图表,可以清楚地看到失败的原因以及失败类型的比例。
4.9 代码覆盖率分析
结合Jacoco,我们可以分析接口自动化的代码覆盖率。
在多台从机上配置Jacoco还是比较复杂的,所以可以开发覆盖配置辅助工具来帮助测试同学,提高效率。
4.10 用例优化方向
除了上图之外,它还将提供用例优化的方向。
通过用例数量的统计图表,我们可以知道哪些服务用例的用例相对较少,哪些环境的用例相对较少,以便我们更有针对性地补充测试用例。
通过失败原因图表,我们可以改进自己用例中“参数化”和“前后动作”的使用,增加测试用例的稳健性。
按在线接口调用量排序的图表。 我们可以有效地知道哪些服务测试用例需要优先维护。 通过表格我们可以看到哪些服务已经被测试用例覆盖,哪些服务没有被覆盖。 我们可以制定用例开发计划,为各组QA提供参考。
同时,在维护自动化接口测试时,你会看到用例评分,以协助QA提高用例编写的质量。