测评

乐高-美团界面自动化测试实践

【keywords start】智能化测评【keywords end】 1. 概述 1.1 接口自动化概述

众所周知,接口自动化测试具有以下特点:

如何完成一个接口自动化测试项目?

我认为,一个“好的”自动化测试项目需要从“时间”、“人力”和“利润”三个方面做出良好的“权衡”。

仅仅因为被测系统发生了一些变化,花费了几个小时的自动化脚本就无法执行。 同时,我们还要看到“利”。 我们不能因为总是希望看到 100% 的成功而少做或不做验证。 但如果验证太多,维护成本肯定会增加,每天可能需要大量的维护。

因此,平衡这三个方面并不容易。 经常看到做自动化的学生最终本末倒置。

1.2 提高投资回报率

要提高ROI(投资回报率),必须从两个方面入手:

降低投入成本。 增加使用量。 以“降低投入成本”为目标

我们需要做的是:

以“增加使用量”为目标

我们需要做的是:

因此,我开发了乐高接口测试平台来实现我的一些自动化测试想法。 首先,简单浏览一下网站,了解一下它是什么样的工具。

首页/

用例维护页面/

自动化用例列表/

在线执行结果/

用例数量统计/

1.3 乐高的组成

乐高接口测试方案由两部分组成,一是你刚才看到的“网站”,二是“脚本”。

我们先来介绍一下“脚本设计”部分。

2. 脚本设计 2.1 乐高的做法

乐高界面自动化测试脚本部分采用了非常常见的Jenkins+TestNG结构。

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配置文件的脚本:




 
 
 
 
 
 
 
 
 
 

 

1/

我们参考上图解释一下配置文件:

这样做有什么好处?

使用SQL的最大优点是灵活性

1/

比如上面的例子,会在数据库中查询下面56个测试用例,然后这个标签就会对这56个测试用例进行一一测试。

当有多个标签时,可以分组显示

1/

使用多个标签来区分用例的最大好处是,在最终的报表中也可以达到分组展示的效果。

报表更美观、更丰富

1/

由于使用ReportNG来打印报告,因此报告显示比TestNG自带的报告更加美观,并且显示风格可以自定义。 点击它可以查看详细的执行过程。

1/

如果有执行失败的用例,通常会在顶部首先显示报告错误的用例。

支持多个团队

1/

当两个团队开始使用时,为了方便维护,基本部分被抽离出来。 各团队的脚本都依赖于这个Base包,并且Base包版本设置为“SNAPSHOT版本”。 使用“SNAPSHOT版本”的好处是,当我以后更新乐高时,各个业务组都可以及时更新,而不需要对脚本进行任何更改。

当更多的团队开始使用它时,它会更直观地看起来像这样:

1/

每个团队的脚本依赖于我的Base包,所以最终每个业务团队的脚本是这样的:

1/

正如你所看到的,使用乐高后:

您还可以使用 Jenkins 来实施预定的构建测试。

由于所有测试用例都在数据库中,所以这个脚本基本上不需要修改,减少了大量的脚本代码。

有同学想问,有时候写接口测试用例不仅仅是请求接口。 可能还需要写一些数据库操作。 您可能需要自己编写一些方法来获取一些参数。 没有代码怎么处理? 呢绒?

让我们进入“用例设计”,我将介绍我如何通过统一的用例模板来解决这些问题。

3. 用例设计 3.1 一些想法

我在做界面自动化设计的时候,会考虑以下几点:通用性、可验证性、健壮性、易用性。

通用模板可用于统计和可扩展验证

在编写自动化脚本时,每个人都希望“一丝不苟”,然后“写很多”检查点; 但当“检查点”过多时,执行就会因为多种原因而失败。 因此,我们的设计需要在保证足够的检查点的同时,尽可能减少误报。

最大限度地减少误报并保持稳健

在测试执行过程中,难免会报错。 执行失败可能的原因有很多,简单分为4类:

1/

对于上述情况:

IP表项变更灵活,可重复使用

通过这些手段,提高了测试用例的健壮性,使每个自动化测试用例都能很好地完成测试任务,真正发挥出测试用例的价值。

易用性和减少代码操作需要3.2乐高界面自动化测试用例

话虽如此,我们还是来看看乐高界面测试用例是什么样子的。

一个乐高自动用例的执行顺序大致如下图:

1/

简单区分一下各个部分,就可以看到:

1/

上图中提到了两个术语:

下面对这两个术语进行简单介绍。

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。

1/

通过配置我们可以看到该参数的值为执行一条SQL后执行结果中DealID字段的值。

(2) 在用例中,将需要此表单编号的地方替换为${shopdealid}。

1/

写测试用例的时候可以看一下这张放大图。 这里的ProductID的值并不是硬编码的固定形式的数字,而是刚刚配置的参数化数据。

(3)执行结果中${shopdealid}变为从实时查询数据库中获取的真实单号。

1/

从结果中可以看到,我们的参数已经被替换为有效值,而这个值是从我们刚刚配置的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,获取库存限额的未售出的团单。

1/

此配置还可以支持在线调试。 调试时可以看到可以使用的参数化:

1/

(2)在测试用例中的前动作中,添加未售出的团单,获取库存上限。

1/

这样就可以用${pre.ProductID}来替换整个测试用例中的原始数据信息。

(3)最后请求接口并返回执行成功。

1/

问答

Q:如果我们也获取3个参数,那么使用3个“参数化Select操作”和使用1个“预操作Select操作”有什么区别?

A:区别在于执行时间。 例如我们查询最新有效团单的“订单号”、“订购人”、“手机号”三个字段。 使用三个“参数化Select操作”:也许在执行${order number}时,得到的订单号是“10001”,但是在执行${order person}时,可能有人下了另一个订单。 下单时,下单的人可能已经变成了“10002”的“李四”,而不是“10001”的“张三”。 最终可能存在“订单号”、“订单人”、“手机号”这三个字段的数据,并不是同一行的数据。 使用“前置Select操作”可以避免上述问题,因为所有字段的数据都是一次性查询出来的,所以不会出现错位的情况。

问:“参数化选择操作”和“预操作选择操作”等不同的取值时机有什么好处?

A:由于“预动作”必须在接口请求之前执行,所以“参数化”只有在使用时才必须执行。 因此,在检查点中,如果要验证某个数据库字段在接口调用后是否发生变化,可以使用“预操作”和“参数化”同时查询该字段,然后进行比较。 如果有任何不一致,就意味着发生了变化。 因此,根据使用场景选择合适的参数化方法非常重要。 选择正确的方法可以大大提高测试用例的测试数据的稳健性。

3.5 执行各部分

回到一开始的流程图,我们可以一一看一下执行过程。

测试已启动

1/

测试启动基本采用Jenkins,稳定、成熟、简单,有公司工具组支持。 它还支持从乐高的网页执行。

数据/环境准备

1/

使用@DataProvider从DB数据库读取测试用例并一一执行进行测试。

测试执行

1/

在测试用例正式执行之前,会进行一波参数替换动作。 调用该接口后,还会进行参数替换动作。

1/

参数替换后,会执行前置动作,调用接口后执行后测试动作,最后执行后置动作。

1/

接口请求部分就不多说了。 就是通过接口请求的参数去请求对应的接口,并得到返回结果。

这里的话是为了方便和通用,所以要求返回的结果是String类型。 这样做的最大好处是。 例如,我现在有一个新的接口类型需要连接。 那么你只需要编写一个方法来请求这个接口并得到String类型的返回结果,就可以快速将新的接口类型连接到乐高测试平台进行接口测试。

检查点

1/

检查点部分是自动化测试用例的本质。 一个自动化测试用例能否真正发挥它的测试功能,取决于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}

检查点是这样的:

“参数化”和“动作前和动作后”也支持递归配置。 这些都是为了让接口自动化测试用例的编写更加灵活、易用。

检测结果

1/

使用 ReportNG 打印精美的报告。

报表会自定义一些显示方式,比如高亮显示。 使用ReportNG前只需添加以下语句即可支持“输出转义”并使用HTML标签自定义输出样式。

System.setProperty("org.uncommons.reportng.escape-output", "false");

 

后期优化

1/

使用Jenkins执行时,会定期通过Jenkins API和Base包中的一些方法获取测试结果,存储在数据库中,并用于生成统计图表。

4. 网站功能 4.1 网站开发

既然打算做一个工具平台,那么方方面面都要设计。 不幸的是,由于缺乏人力和时间,我只能在下班时间进行开发。 可以看作承担了乐高平台的产品、后端开发、前端开发、运维、测试等各种角色。

Jenkins+TestNG+ReportNG+我自己开发的基本接口自动化测试基础jar包基本上不会太难。 但对于网站来说,在来美团之前,我从来没有开发过这样的工具平台。 这是我的第一个带有网络界面的工具。 我当时正在谷歌工作,但没想到很快就构建了一个简单的版本。

采用Servlet+Jsp进行开发。 前端框架使用Bootstrap,前端数据使用jstl,数据库使用MySQL,服务器使用公司的Beta环境Docker虚拟机。 该域名为申请的公司内网域名,开放于北京、上海。 网络访问权限。

功能基本要满足。 界面虽然不能惊艳,但也不能丑陋。 功能还算满意,但界面看起来像是20世纪80年代的。 我会厌恶使用它,所以我仍然更喜欢它的界面。 花了一些时间来调整和设计。 一旦习惯了,速度会快得多。

4.2 总体构成

1/

Lego目前由五个不同的项目组成,分别是“测试脚本”、“Lego-web page项目”、“执行接口测试的基础包”、“小工具集合Lego-kit”和“lego-job”,可以看到依赖关系通过上图可以了解各个项目之间的关系。

各个项目的详细功能如下:

1/

简单来说就是网站部分和脚本分离,中间的链接就是数据库。 因此,没有网站的情况下脚本执行是没有问题的; 同样,网站的运行与脚本无关。

4.3 使用-日常维护步骤1

1/

您每天上班时都会收到这样一封测试电子邮件。 通过邮件可以了解昨晚的执行情况。 如果报告错误,可以点击“详细报告链接”跳转到在线报告。

第2步

1/

在当前报告中,您可以直接看到执行错误信息,然后点击“乐高维护门户”即可跳转到乐高站点进行用例维护。

步骤3

跳转到站点后,可以直接显示测试用例的所有信息。 定位、维护、保存、维护用例,可以点击“执行”查看维护后的执行结果。 维护完毕后,只需“保存”即可。

只需3步,1~2分钟即可完成失败用例的定位、调试和维护。

4.4 用例编辑

1/

通过该页面,我们可以执行一个测试用例:

4.5 在线调试

1/

lego-web项目也使用base来执行用例,因此执行结果和打印与脚本执行一致。

4.6 用例生成工具

为了更方便的编写用例,针对部分接口开发了一个一键批量生成用例的小工具。

4.7 执行结果分析

通过Jenkins接口和Base包中的基本Test方法,将结果收集到数据库中,方便各组分析测试结果。

1/

这是每日执行后的成功率图表:

1/

还可以按月进行统计,生成统计图表,帮助各团队收集、整理月报数据。

4.8 故障原因追踪

通过直观地显示测试结果的图表,您将需要跟踪失败的原因。

1/

因此,在成功率数据的右侧,会有一个跟踪失败原因的条目,也可以直观地看到哪些失败原因没有被跟踪。 点击后可以记录失败的原因。

1/

最后会生成一个图表,可以清楚地看到失败的原因以及失败类型的比例。

4.9 代码覆盖率分析

结合Jacoco,我们可以分析接口自动化的代码覆盖率。

1/

在多台从机上配置Jacoco还是比较复杂的,所以可以开发覆盖配置辅助工具来帮助测试同学,提高效率。

1/

4.10 用例优化方向

除了上图之外,它还将提供用例优化的方向。

1/

通过用例数量的统计图表,我们可以知道哪些服务用例的用例相对较少,哪些环境的用例相对较少,以便我们更有针对性地补充测试用例。

1/

通过失败原因图表,我们可以改进自己用例中“参数化”和“前后动作”的使用,增加测试用例的稳健性。

1/

按在线接口调用量排序的图表。 我们可以有效地知道哪些服务测试用例需要优先维护。 通过表格我们可以看到哪些服务已经被测试用例覆盖,哪些服务没有被覆盖。 我们可以制定用例开发计划,为各组QA提供参考。

1/

同时,在维护自动化接口测试时,你会看到用例评分,以协助QA提高用例编写的质量。