测试作为软件开发过程中相对耗时但必不可少的一个环节,在研效提升的大背景下,如何保证测试场景的覆盖度的同时,进一步提升测试研效成为值得关注的话题。基于模型的测试,即 MBT (Model-Based-Testing),可能是一个新的解法。简单来说,MBT 属于自动化测试,是通过被测系统的逻辑模型自动生成测试用例的技术,能够帮助缩短测试场景梳理以及手工测试自动化的耗时。本文将主要介绍MBT的原理和使用方法,以及在计费系统上的实践,希望能在是否使用MBT以及如何使用MBT上给大家提供一些参考。
基于模型的测试,即 Model Based Testing,简称 MBT。
通过被测系统的流程逻辑模型,结合个性化算法和策略来遍历流程模型,以此生成测试用例场景。基于模型的测试的有效性主要体现在它提供了测试场景自动化的可能。如果是一个机器可读的模型,并且具有定义良好的行为解释,那么原则上可以通过遍历自动地派生测试用例。
MBT 按照自动化程度可分为三个等级:
手工测试:通过对被测系统进行建模后,获取执行流程,手工编写用例,手工执行用例
半自动化测试:通过对被测系统进行建模后,获取执行流程,自动生成用例文件后,手工执行用例。半自动MBT和手动MBT的区别是是否使用了通过模型生成抽象测试用例的引擎。
全自动化测试:通过对被测系统进行建模后,获取执行流程,自动生成用例场景,自动执行用例。在全自动化测试中,不同于半自动测试,会自动执行生成的用例场景。在某些场景下,不会生成具体的可执行用例文件。
MBT 整体流程可分为需求,模型,用例,执行,报告,归档六个阶段。
概括一下就是,开发/测试人员按照产品需求,构建被测系统流程模型,将模型与被测系统用例模板相结合形成测试用例,执行测试用例后获得版本测试报告,最后将系统模型归档,供后续版本复用。
MBT 中对于模型的描述方式没有特殊限制,支持UML,FSM(有限状态机),Markov chain(马尔可夫链)等。 本文中说明中使用的模型默认为FSM。
在需求到模型的过程中,首先需要梳理出被测系统主要动作(Action) 和期望结果(Check)。
主要动作(Action) 即为被测系统的用户可能涉及的业务逻辑,如对于Web应用,刷新网页、输入错误密码、发起商品下单等关键行为。
期望结果(Check) 即为产品需求中用户在完成主要动作(Action)后被测系统进入的状态。如网页登录场景下,用户输入错误密码,期望结果(Check) 为展示错误信息,并保持在登录界面不跳转。
以投币门闸为例,投币门闸为某一区域提供自动收费放行能力,投币后可通行,未投币不放行。
以FSM模型为例,在MBT场景下,FSM有四个关键元素:
下图为投币门闸 FSM 流程图:
可以发现需求和模型的共通之处:
此时被测系统产品需求已经变成了 FSM 模型,将FSM模型描述生成用例可以分为三个步骤:“Model as Code”,模型路径遍历,生成用例文件
这一步的目的很明确,需要将FSM模型转换为机器可识别的代码,供后续流程使用该模型。可识别的代码规范通常取决于下一步中的模型路径遍历算法,路径遍历算法可识别即可。
模型遍历中,有两个关键因素:遍历算法和停止条件。
遍历算法决定如何遍历模型,将会直接影响生成的用例逻辑。不同的遍历算法,会生成不同的主要动作执行和期望结果检查顺序,需要按照场景调整遍历算法。常用的遍历算法包括:完全/权重随机,最短路径
停止条件决定什么情况下遍历停止,会影响生成用例的整体覆盖率。常用标准有边覆盖率,顶点覆盖率,路径长度和不停止。
在考虑模型路径遍历时,需要根据不同场景,选择合适的遍历算法和停止条件。按照实际经验,后台系统大部分通过最短路径算法和边与顶点全覆盖来生成用例可以满足场景覆盖的需求。
通过上一步的遍历模型后,可以获得所有用例执行逻辑,如投币闸机的一条成功用例:
V_Locked -> E_Coin -> V_Unlocked -> E_Push -> V_Locked
通过执行逻辑生成用例文件还需要为每一个主要动作(action)和期望结果(check) 编写代码模块, 用例通过lib的方式引用,同一系统可共用代码模块。
# 投币操作
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
生成的用例文件的执行逻辑部分如下:
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)
用例模板其他部分可按照需求自由增加,如请求参数初始化模块,系统状态初始化模块等等。
在上文中有介绍MBT的半自动化测试和全自动化测试,它们的区别在于是否自动执行用例。按照步骤一和步骤二,我们已经得到了可执行用例文件,已经达到了半自动化MBT测试的程度。那么如何将用例生成和用例执行流程打通,实现全自动MBT测试呢?
分享我使用过的两种方案:
1. 蓝盾流水线:在蓝盾流水线中完成用例生成&执行自动化串联,将自动生成的测试用例通过脚本的方式批量执行,生成测试报告。可通过html或企业微信推送的形式推送给流水线发起人。
2. QT4S 用例平台: 按照QT4S用例模板格式生成用例后,自动上传Git,通过配置QT4S hook 可实现自动拉起新用例。QT4S 也提供了详细的执行报告能力。
对于逻辑较复杂的被测系统,在需求分解和建模阶段会有比较高的时间成本,推荐在用例生成完成后,将模型归档之Git,后续待测系统新版本改动可直接复用。
Graphwalker 是一款Java 编写的开源MBT工具,可实现全自动MBT测试。可分为两个主要模块:
渠道 svr 作为计费系统中的关键模块,提供订单管理和与外部渠道方交互的能力。对于每一外部渠道方,计费系统中都有一个对齐对应的单独的渠道svr提供计费接入能力,即使是不同的渠道 svr 的业务流程也大致相似。
因此,从整体流程上看,不同渠道svr的主要动作(Action) 和期望结果(Check)大致相同,系统模型可多次复用,非常适合通过MBT的方式生成测试用例。
上图为根据渠道svr系统逻辑,构建的场景模型,其中包含了主流程和异常处理流程,覆盖了人工测试中所有需要校验的场景。
通过 GraphWalker Studio 在网页完成渠道svr 的建模(上图)后,可以得到 json 模型描述,其中有三个需要注意的地方:
.models[0].edges
用于定义所有流程中主要动作(Action).models[0].vertices
用于定义所有流程中主要动作(Action).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 模型如下:
{
"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, 遍历完成,会返回的路径规划。每一行代表一个测试场景对应一个测试用例。
e_
开头的节点为 Edge (关键动作)v_
开头的节点为 Vertex (结果检查)具体模型返回路径,如下:
["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) 字符串拼接函数的部分代码:
# 渠道通用步骤检查范例
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
为了提高整体接入效率,提供了一套MBT接入配置生成能力,通过 Web 解析渠道 svr 系统日志自动获取了请求参数以及必要信息后系统将配置自动上传Git,供用例生成系统使用。如果不需要特殊配置,无需人工调整即可直接生成用例。
MBT本质上是一种依赖被测系统模型的测试方法,在模型覆盖全面的前提下,相较于其他测试方法,MBT有着更高的测试自动化程度以及更高场景覆盖度。但是不同项目的需求有着天壤之别,为满足个性化项目的需要,每个项目需要进行定制开发,使用初期会带来较大成本。与此同时,由于对模型的依赖度高,对于模型的维护有较高的要求,这也带来了更高的学习成本。
MBT 并不是软件测试效能提升的一颗 “银弹”。因此,在了解了 MBT 原理的前提下,我们需要综合考虑项目特点以及后续方向,再决定是否使用MBT,只有这样才能实现 MBT 能力的最大化。
优点一:测试用例自动生成,无需人工编写
MBT工具可以按照提供的用例模板,自动生成所有路径的测试用例,无需人工编写,降低手工测试自动化耗时。
优点二:模型可复用
MBT 通过图形化界面对业务系统进行建模后,模型可在不同版本间复用。业务系统首次接入后,后续使用成本较低。同时业务系统模型帮助梳理测试场景,可以提高测试场景梳理效率。
缺点一:初期投入较大
Graphwalker 其实不能完全满足我们的项目需求,需要做一些定制改动,另外由于部门内系统复杂度较高,工具必须是可扩展的,并且能够处理复杂的测试逻辑,提供足够高的测试覆盖率,整体开发成本高。后续拓展能力所需成本也较高。
缺点二:学习成本高
学习成本高体现在两个方面:工具学习成本和模型学习成本。
工具学习成本指的是,需要理解定制的MBT工具如何运作,需要学习并理解在特定场景下使用哪种特定的路径遍历策略等。
模型学习成本指的是,对模型依赖高,测试人员需要对被测系统非常熟悉,模型对于被测系统场景覆盖率要求高,且模型协议需要严格遵守并妥善维护。
《基于模型的测试:一个软件工艺师的方法》
https://book.douban.com/subject/34467658/
如果对MBT感兴趣的同学可以读一下这本书,对于如何理解被测系统,如何选择合适辅助工具,以及如何使用MBT工具有很详细的介绍。不过这本书理论较多,适合在项目实践简单的MBT项目后阅读。
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/
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。