高效率、重覆盖的测试用例自动生成之法 - Model Based Testing

2022-08-09 19:38:54 浏览数 (1)

测试作为软件开发过程中相对耗时但必不可少的一个环节,在研效提升的大背景下,如何保证测试场景的覆盖度的同时,进一步提升测试研效成为值得关注的话题。基于模型的测试,即 MBT (Model-Based-Testing),可能是一个新的解法。简单来说,MBT 属于自动化测试,是通过被测系统的逻辑模型自动生成测试用例的技术,能够帮助缩短测试场景梳理以及手工测试自动化的耗时。本文将主要介绍MBT的原理和使用方法,以及在计费系统上的实践,希望能在是否使用MBT以及如何使用MBT上给大家提供一些参考。

1、什么是MBT

基于模型的测试,即 Model Based Testing,简称 MBT。

1.1、基本原理

通过被测系统的流程逻辑模型,结合个性化算法和策略来遍历流程模型,以此生成测试用例场景。基于模型的测试的有效性主要体现在它提供了测试场景自动化的可能。如果是一个机器可读的模型,并且具有定义良好的行为解释,那么原则上可以通过遍历自动地派生测试用例。

1.2、MBT 自动化程度分级

MBT 按照自动化程度可分为三个等级:

手工测试:通过对被测系统进行建模后,获取执行流程,手工编写用例,手工执行用例

半自动化测试:通过对被测系统进行建模后,获取执行流程,自动生成用例文件后,手工执行用例。半自动MBT和手动MBT的区别是是否使用了通过模型生成抽象测试用例的引擎。

全自动化测试:通过对被测系统进行建模后,获取执行流程,自动生成用例场景,自动执行用例。在全自动化测试中,不同于半自动测试,会自动执行生成的用例场景。在某些场景下,不会生成具体的可执行用例文件。

2、MBT 整体流程

MBT 整体流程可分为需求,模型,用例,执行,报告,归档六个阶段。

MBT 整体流程MBT 整体流程

概括一下就是,开发/测试人员按照产品需求,构建被测系统流程模型,将模型与被测系统用例模板相结合形成测试用例,执行测试用例后获得版本测试报告,最后将系统模型归档,供后续版本复用。

2.1、需求 -> 模型

MBT 中对于模型的描述方式没有特殊限制,支持UML,FSM(有限状态机),Markov chain(马尔可夫链)等。 本文中说明中使用的模型默认为FSM。

2.1.1、需求分解

在需求到模型的过程中,首先需要梳理出被测系统主要动作(Action)期望结果(Check)

主要动作(Action) 即为被测系统的用户可能涉及的业务逻辑,如对于Web应用,刷新网页、输入错误密码、发起商品下单等关键行为。

期望结果(Check) 即为产品需求中用户在完成主要动作(Action)后被测系统进入的状态。如网页登录场景下,用户输入错误密码,期望结果(Check) 为展示错误信息,并保持在登录界面不跳转。

以投币门闸为例,投币门闸为某一区域提供自动收费放行能力,投币后可通行,未投币不放行。

  • 投币门闸系统的主要动作(Action) 为推门闸栏杆投币
  • 期望结果(Check) 为门闸上锁门闸解锁。用户完成主要动作投币后,系统进入门闸解锁状态。用户推动栏杆进入后,系统进入门闸上锁状态。
2.1.2、构建模型

以FSM模型为例,在MBT场景下,FSM有四个关键元素:

  1. 初始状态:某一需求下,系统的初始状态
  2. 结束状态:某一需求下,系统的结束状态,表示场景终止
  3. 运行状态:表示系统正在运行中,既不是需求状态起点也不是状态终止点
  4. 输入动作:表示会系统会接受到的输入,包括会导致状态发生变化的动作,也包括不会导致状态发生变化的动作。

下图为投币门闸 FSM 流程图:

投币门闸 FSM 流程图投币门闸 FSM 流程图
  1. 初始状态:已上锁
  2. 结束状态:已上锁和已解锁 结束状态为门闸已上锁场景:不投币,门闸保持上锁状态,推栏杆,无法进入 结束状态为门闸已解锁场景:投币后,门闸解锁,推栏杆,进入成功
  3. 运行状态:已上锁和已解锁状态同时为运行状态
  4. 输入动作:投币和推栏杆
2.1.3、需求&模型结合

可以发现需求和模型的共通之处:

  • 需求中的主要动作为模型中的输入动作
  • 需求中的期望结果为模型中的状态

2.2、模型 -> 用例

此时被测系统产品需求已经变成了 FSM 模型,将FSM模型描述生成用例可以分为三个步骤:“Model as Code”模型路径遍历生成用例文件

2.2.1、"Model as Code"

这一步的目的很明确,需要将FSM模型转换为机器可识别的代码,供后续流程使用该模型。可识别的代码规范通常取决于下一步中的模型路径遍历算法,路径遍历算法可识别即可。

2.2.2、遍历模型路径

模型遍历中,有两个关键因素:遍历算法停止条件

遍历算法决定如何遍历模型,将会直接影响生成的用例逻辑。不同的遍历算法,会生成不同的主要动作执行和期望结果检查顺序,需要按照场景调整遍历算法。常用的遍历算法包括:完全/权重随机,最短路径

停止条件决定什么情况下遍历停止,会影响生成用例的整体覆盖率。常用标准有边覆盖率,顶点覆盖率,路径长度和不停止。

在考虑模型路径遍历时,需要根据不同场景,选择合适的遍历算法和停止条件。按照实际经验,后台系统大部分通过最短路径算法边与顶点全覆盖来生成用例可以满足场景覆盖的需求。

2.2.3、生成用例文件

通过上一步的遍历模型后,可以获得所有用例执行逻辑,如投币闸机的一条成功用例:

V_Locked -> E_Coin -> V_Unlocked -> E_Push -> V_Locked

  • E 表示 edge 边,为主要动作(action)
  • V 表示 vertex 顶点,为期望结果(check)
  • 该用例表示闸机初始状态为已上锁,执行投币操作后,校验闸机是否已解锁,然后执行推栏杆操作后,校验闸机是否已上锁。

通过执行逻辑生成用例文件还需要为每一个主要动作(action)和期望结果(check) 编写代码模块, 用例通过lib的方式引用,同一系统可共用代码模块。

代码语言:python代码运行次数:4复制
# 投币操作
def put_coin(user, turnstile):
	coin = user.get_coin_from_pocket()
	user.put_coin(turnstile, coin)

# 推门闸栏杆操作
def push(user, turnstile):
	user.push(turnstile)
	
# 判断是否为上锁状态
def determine_if_locked(turnstile):
	locked = turnstile.if_locked()
	return locked
	
# 判断是否为解锁状态
def determine_if_unlocked(turnstile):
	locked = turnstile.if_locked()
	return not locked

通过用例

V_Locked -> E_Coin -> V_Unlocked -> E_Push -> V_Locked

生成的用例文件的执行逻辑部分如下:

代码语言:python代码运行次数:1复制
user = User()
turnstile = Turnstile()

# V_Locked
result = determine_if_locked(turnstile)
check_result(result)

# E_Coin
put_coin(user, turnstile)

# V_Unlocked
result = determine_if_unlocked(turnstile)
check_result(result)

# E_Push
push(user, turnstile)

# V_Locked
result = determine_if_locked(turnstile)
check_result(result)

用例模板其他部分可按照需求自由增加,如请求参数初始化模块,系统状态初始化模块等等。

2.3、用例 -> 执行 -> 报告

在上文中有介绍MBT的半自动化测试和全自动化测试,它们的区别在于是否自动执行用例。按照步骤一和步骤二,我们已经得到了可执行用例文件,已经达到了半自动化MBT测试的程度。那么如何将用例生成和用例执行流程打通,实现全自动MBT测试呢?

分享我使用过的两种方案:

1. 蓝盾流水线:在蓝盾流水线中完成用例生成&执行自动化串联,将自动生成的测试用例通过脚本的方式批量执行,生成测试报告。可通过html或企业微信推送的形式推送给流水线发起人。

2. QT4S 用例平台: 按照QT4S用例模板格式生成用例后,自动上传Git,通过配置QT4S hook 可实现自动拉起新用例。QT4S 也提供了详细的执行报告能力。

2.4、 最后一步 - 模型归档

对于逻辑较复杂的被测系统,在需求分解和建模阶段会有比较高的时间成本,推荐在用例生成完成后,将模型归档之Git,后续待测系统新版本改动可直接复用。

3、工具介绍

3.1、Graphwalker

Graphwalker 是一款Java 编写的开源MBT工具,可实现全自动MBT测试。可分为两个主要模块:

  1. Graphwalker Studio: Web 端工作台,如上图所示,可用于模型构建,执行用例等功能。个人项目中,仅使用了Graphwalker Studio 的模型构建能力。执行用例的能力会对整个Graphwalker工程有较深的绑定,成本较高。
  2. Graphwalker Cli:提供模型遍历能力,以Jar包形式提供。 项目中,主要使用了这个Jar 包实现不同策略的模型遍历功能。

4、在计费项目中的实际使用

4.1、渠道SVR MBT 测试实践

渠道 svr 作为计费系统中的关键模块,提供订单管理和与外部渠道方交互的能力。对于每一外部渠道方,计费系统中都有一个对齐对应的单独的渠道svr提供计费接入能力,即使是不同的渠道 svr 的业务流程也大致相似。

因此,从整体流程上看,不同渠道svr的主要动作(Action)期望结果(Check)大致相同,系统模型可多次复用,非常适合通过MBT的方式生成测试用例。

模型示意图模型示意图

上图为根据渠道svr系统逻辑,构建的场景模型,其中包含了主流程和异常处理流程,覆盖了人工测试中所有需要校验的场景。

"Model as Code"

通过 GraphWalker Studio 在网页完成渠道svr 的建模(上图)后,可以得到 json 模型描述,其中有三个需要注意的地方:

  1. .models[0].edges 用于定义所有流程中主要动作(Action)
  2. .models[0].vertices 用于定义所有流程中主要动作(Action)
  3. .models[0].generator 表示使用的路径遍历算法, 渠道svr 模型使用的是 random(edge_coverage(100)&&vertex_coverage(100)) 表示一直通过随机选择路径的方式遍历路径,直到达到 Edge 和 Vertax 覆盖率均达到 100%。 详细介绍: https://github.com/GraphWalker/graphwalker-project/wiki/Generators-and-stop-conditions

具体json 模型如下:

代码语言:json复制
{
    "models": [
        {
            // 1、Edge 表示主要动作(Action)
            "edges": [
                {
                    "id": "e0",
                    "name": "e_get_pay_info",
                    "sourceVertexId": "v1",
                    "targetVertexId": "v2"
                }
                // 省略...
            ],
            // 3、 generator 表示选择的路径遍历算法
            "generator": "random(edge_coverage(100)&&vertex_coverage(100))",
            "name": "NewModel1",
            "startElementId": "v1",
            // 2、 Vertex 表示期望结果(Check) 
            "vertices": [
                {
                    "id": "v1",
                    "name": "start",
                    "properties": {
                        "blocked": false
                    }
                },
                {
                    "id": "v2",
                    "name": "v_get_pay_info_succ"
                }
                // 省略...
            ]
        }
    ],
    "name": "DefaultModels"
}
遍历模型路径

将模型的json描述传入 Graphwalker Cli, 遍历完成,会返回的路径规划。每一行代表一个测试场景对应一个测试用例。

  • 测试场景始终以 Vertax Start 为起点。
  • e_ 开头的节点为 Edge (关键动作)
  • v_ 开头的节点为 Vertex (结果检查)

具体模型返回路径,如下:

代码语言:txt复制
["start", "e_get_pay_info", "v_get_pay_info_succ"],
["start", "e_portal_get_pay_info", "v_get_pay_info_succ"],
["start", "e_portal_get_pay_info_spec_uin", "v_get_pay_info_succ"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_get_pay_info", "v_get_pay_info_succ"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_pay_cancel", "v_order_status_error"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_pay_confirm", "v_order_status_error"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_notify_pay_result", "v_notify_pay_result_succ"],
["start", "e_portal_get_pay_info", "v_get_pay_info_succ", "e_provide_cgi_provide", "v_pay_confirm_succ"],
["start", "e_portal_get_pay_info_spec_uin", "v_get_pay_info_succ", "e_provide_cgi_provide", "v_pay_confirm_succ"]
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_get_pay_info", "v_get_pay_info_succ", "e_get_pay_info", "v_get_pay_info_succ"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_get_pay_info", "v_get_pay_info_succ", "e_notify_pay_result", "v_notify_pay_result_succ"],
["start", "e_get_pay_info", "v_get_pay_info_succ", "e_notify_pay_result", "v_notify_pay_result_succ", "e_verify_order", "v_verify_order_succ"]]
生成用例文件

在渠道svr的用例生成中,可能有其他人需要复用我的用例以及拷贝编辑等操作,出于这个考虑,我没有选择通过lib引用的方式生成用例文件,而是将用例和MBT工具解耦。

我选择了字符串拼接的方式生成用例。和lib库引用方法的大致原理相同,区别在于字符串拼接通过代码库字符串的形式拼接用例,lib引用则是直接引用MBT库中的函数。

以下是主要动作(Action) 和期望结果(Check) 字符串拼接函数的部分代码:

代码语言:python代码运行次数:1复制
# 渠道通用步骤检查范例
def v_channel_check_comm(order_status=0, result_code=0, result_info="ok"):
  if order_status:
    assign_expect_order_status = '''expect_order_status = "{order_status}"'''.format(order_status=order_status)
  else:
    assign_expect_order_status = ""

  assign_expect_result_code = '''self.itest_assert_equal("返回码校验", rsp.rsp_body[RETURN_RESULT], '{code}')'''.format(
    code=result_code)
  assign_expect_result_info = '''self.itest_assert_equal("返回码校验", rsp.rsp_body[RETURN_RESULT_DESC], '{info}')'''.format(
    info=result_info)

  code = '''
    # ------------------------------
    self.start_step("校验回包结果")
    {assign_expect_result_code}
    {assign_expect_result_info}

    self.start_step("DB校验")
    {assign_expect_order_status}
    sql = select_join(configure.ORDER_DB["order_table"], configure.ORDER_DB["database"],
                      "FOrderId = '"   order_id   "'", "*")
    result = dbreq.send(configure.ORDER_DB, sql)
	
    self.itest_assert_equal("正确", result["ITEST_ANS"][0]["FStatus"], expect_order_status)
    '''.format(assign_expect_order_status=assign_expect_order_status,
               assign_expect_result_code=assign_expect_result_code,
               assign_expect_result_info=assign_expect_result_info)

  return code
4.1.2、测试请求参数如何获取

为了提高整体接入效率,提供了一套MBT接入配置生成能力,通过 Web 解析渠道 svr 系统日志自动获取了请求参数以及必要信息后系统将配置自动上传Git,供用例生成系统使用。如果不需要特殊配置,无需人工调整即可直接生成用例。

5、总结

MBT本质上是一种依赖被测系统模型的测试方法,在模型覆盖全面的前提下,相较于其他测试方法,MBT有着更高的测试自动化程度以及更高场景覆盖度。但是不同项目的需求有着天壤之别,为满足个性化项目的需要,每个项目需要进行定制开发,使用初期会带来较大成本。与此同时,由于对模型的依赖度高,对于模型的维护有较高的要求,这也带来了更高的学习成本。

MBT 并不是软件测试效能提升的一颗 “银弹”。因此,在了解了 MBT 原理的前提下,我们需要综合考虑项目特点以及后续方向,再决定是否使用MBT,只有这样才能实现 MBT 能力的最大化。

5.1、优点

优点一:测试用例自动生成,无需人工编写

MBT工具可以按照提供的用例模板,自动生成所有路径的测试用例,无需人工编写,降低手工测试自动化耗时。

优点二:模型可复用

MBT 通过图形化界面对业务系统进行建模后,模型可在不同版本间复用。业务系统首次接入后,后续使用成本较低。同时业务系统模型帮助梳理测试场景,可以提高测试场景梳理效率。

5.2、缺点

缺点一:初期投入较大

Graphwalker 其实不能完全满足我们的项目需求,需要做一些定制改动,另外由于部门内系统复杂度较高,工具必须是可扩展的,并且能够处理复杂的测试逻辑,提供足够高的测试覆盖率,整体开发成本高。后续拓展能力所需成本也较高。

缺点二:学习成本高

学习成本高体现在两个方面:工具学习成本和模型学习成本。

工具学习成本指的是,需要理解定制的MBT工具如何运作,需要学习并理解在特定场景下使用哪种特定的路径遍历策略等。

模型学习成本指的是,对模型依赖高,测试人员需要对被测系统非常熟悉,模型对于被测系统场景覆盖率要求高,且模型协议需要严格遵守并妥善维护。

6、拓展阅读推荐

《基于模型的测试:一个软件工艺师的方法》

https://book.douban.com/subject/34467658/

如果对MBT感兴趣的同学可以读一下这本书,对于如何理解被测系统,如何选择合适辅助工具,以及如何使用MBT工具有很详细的介绍。不过这本书理论较多,适合在项目实践简单的MBT项目后阅读。

7、Source

1、finite state machine: https://en.wikipedia.org/wiki/Finite-state_machine

2、graphwalker: https://graphwalker.github.io/

3、基于模型的测试:一个软件工艺师的方法: https://book.douban.com/subject/34467658/

0 人点赞