得物 App:基于 NebulaGraph 的精准测试平台
本文发布于「NebulaGraph 公众平台」,更多产品资讯请访问「NebulaGraph 官网」
一、精准测试介绍
什么是精准测试?
目前业内尚未形成统一的精准测试实践方案,对其也有多种定义:
(1)精准测试是一套计算机测试辅助分析系统。精准测试的核心组件包含的软件测试示波器、用例和代码的双向追溯、智能回归测试用例选取、覆盖率分析、缺陷定位、测试用例聚类分析、测试用例自动生成系统,这些功能完整的构成了精准测试技术体系。
(2)精准测试是一种测试方法,旨在通过优化测试过程和测试结果,提高软件质量和可靠性。该测试方法结合了自动化和手动化测试技术,以尽可能地覆盖所有的功能和边缘情况,从而减少潜在的缺陷和错误。
为什么要做精准测试?
精准测试是为了解决测试的质量和效率问题。
传统测试主要依赖测试人员的自身经验结合具体需求,分析出可能涉及的功能模块以和受影响的功能模块,从而评估出测试范围。如果想提高精准度,则需要沉浸到代码中去寻找答案,不仅提高了人力成本,更是对测试同学提出了更高的要求,更不适用于敏捷迭代中。
常见的精准测试方案
精准测试核心有两方面:精准推荐(用例推荐、接口、方法等)、测试度量(度量代码被测情况即代码覆盖率)
二、得物精准测试平台介绍
得物精准测试平台主要负责推荐精准测试范围,减少盲测、漏测、冗测现象。并将 “精准推荐 + 用例执行 + 结果度量”结合形成有机整体,在测试前、测试中、测试后帮助质量团队保质量、提效率。
目前平台已提供服务端 Java/Golang 应用和前端 H5 应用的精准推荐能力。
相比于上文提到的精准测试的定义,其实我们更关注它能给我们带来什么帮助,我们期望精准测试实现以下目标:
-
推荐测试范围,减少盲测、漏测、冗测,降低上线风险,提高测试准度和效率
-
测试前:评估影响面(增量影响面,风险影响面(如:资损、核心等),自动跑用例,前置发现问题
-
测试中:暴露未测代码,降低漏测风险,引导测试方向
-
测试后:度量测试覆盖度,找出卡点,保证上线质量
三、得物精准推荐平台的实现方案
得物精准测试平台主要由“精准测试平台”和“推荐引擎(链路分析器、差异分析器、知识库、推荐引擎)”两部分构成。
整体思路是建立由“代码行”反推“代码方法”并逆向追踪“API接口”,以“API接口”为枢纽,推荐“自动化用例、功能用例、资损用例等内容”的精准推荐体系。
我们建立完链路分析器后,会输出完整的方法调用链原数据和 API 入口方法标记信息,即建立了方法与方法、方法与类、类与类、类与接口(interface)、接口(interface)与接口(interface)之间的第一层关系,如:当前方法调用了哪些方法以及当前方法被哪些方法调用了。
但仅有原数据还不能进行变更接口推荐,因为原数据中只维护了一层调用关系,需要借助图数据库来把这些原数据串成一张调用链关系网。
为什么是图数据库?
一方面,由于得物精准测试平台“服务端”推荐技术底层是依赖“方法调用链”的,需要提供多版本推荐和实时查看能力。
另一方面,热点应用总方法量大、方法调用关系复杂、应用数量多、代码版本多、链路收集频率高,传统关系型数据库无法满足这种复杂场景,所以需要寻找一款合适的数据库。
经过调研,得物最终选择使用 NebulaGraph 图数据库来作为底层存储库。
NebulaGraph
“图”在人们的日常生活中出现的频率非常高,开心时发个靓照到朋友圈、网购时看完卖家秀再去评论区看看买家秀,这些都涉及到“图”,也许你想象的图是这样的 ↓↓↓
但 NebulaGraph 的“图”并非日常生活中常指的“图”。日常说的图一般指的的图片(Image),这里所说的图( Graph)其实是一种“关系图”,更侧重体现事物之间的关系。
一张图(Graph)由一些小圆点(称为顶点或节点,即 Vertex 或 Node)和连接这些圆点的直线或曲线(称为边,即 Edge 或 Relationship)组成。
用 NebulaGraph 探索《权力的关系》人物关系图谱,最终呈现效果这样的 ↓↓↓
NebulaGraph 是一款开源的分布式高性能图数据库,在 DB-Engines 排名中位列全球图数据库第二,专门用于存储和处理巨大规模的图数据,擅长处理千亿节点万亿条边的超大规模数据集,并且提供毫秒级查询:
-
采用了分布式架构,将图数据分片存储在多个服务器上,以实现水平扩展和高可用性
-
提供了丰富的功能,包括顶点和边的属性和索引管理、多层次的图模型、高效的图遍历算法等
-
支持查询语言 Cypher、GQL、nGQL
得物 NebulaGraph ORM
由于 NebulaGraph 对于 Python 没有提供符合我们预期的 ORM 库,返回的结果是原生的数据格式,使用起来比较麻烦,所以我们自研了轻量级的 NebulaGraph ORM.
我们的精准测试方法的调用链中,主要涉及到 2 种点类型和 4 种边类型,将方法和类(类或接口)设计成 2 种点类型,将方法与方法、类与类、方法与类、变更方法与类这四种关系设计成“边类型”,具体模型定义如下:
class JMethodTag(Tag):Java 方法-点类型,存储方法的相关属性信息
class JMethodTag(Tag):
"""
Java方法-点类型
"""
__NAME_PREFIX__ = 'tjm_'
__VID_PREFIX__ = 'tjm_' # vid前缀
vid = Vid('vid', String, nullable=False, comment='点ID')
name = Field('name', String, nullable=False, comment='简称')
sign = Field('sign', String, nullable=False, comment='签名(全称)', index_len=50)
type = Field('type', String, default='', nullable=False, comment='方法类型(method,constructor,static,get_set,field)')
pkName = Field('pkName', String, default='', nullable=False, comment='包名')
abs = Field('abs', Bool, default=False, nullable=False, comment='是否是抽象类')
api = Field('api', Bool, default=False, nullable=False, comment='是否是API接口', index_len=None)
aPtl = Field('aPtl', String, default='', nullable=False, comment='接口协议(http,dubbo,grpc)', index_len=20)
aPath = Field('aPath', String, default='', nullable=False, comment='API接口路径(http: /xx/xx, dubbo: com.xx.xx.Cxx::mxx, /grpc.CntCenterService/MGetTrendInfo)', index_len=100)
cName = Field('cName', String, default='', nullable=False, comment='所属类简称')
cSign = Field('cSign', String, default='', nullable=False, comment='所属类签名', index_len=50)
cType = Field('cType', String, default='', nullable=False, comment='所属类类型(class,interface,annotation,enum,unknown)')
parNames = Field('parNames', String, default='', nullable=False, comment='参数名(多个";"分割)')
parTypes = Field('parTypes', String, default='', nullable=False, comment='参数类型名(多个";"分割)')
retType = Field('retType', String, default='', nullable=False, comment='返回类型名')
calls = Field('calls', String, default='', nullable=False, comment='调用哪些方法调用(多个";"分割)')
usages = Field('usages', String, default='', nullable=False, comment='被哪些方法调用(多个";"分割)')
sLine = Field('sLine', Int, default=0, nullable=False, comment='开始行(从1开始)')
eLine = Field('eLine', Int, default=0, nullable=False, comment='结束行')
zsScan = Field('zsScan', String, default='', nullable=False, comment='资损字段')
class JClassTag(Tag):Java类-点类型,存储类/接口的相关属性信息
class JClassTag(Tag):
"""
Java类-点类型
"""
__NAME_PREFIX__ = 'tjc_'
__VID_PREFIX__ = 'tjc' # vid前缀
vid = Vid('vid', String, nullable=False, comment='点ID')
name = Field('name', String, nullable=False, comment='简称')
sign = Field('sign', String, nullable=False, comment='签名(全称)', index_len=50)
type = Field('type', String, default='', nullable=False, comment='类类型(class,interface,annotation,enum,unknown)')
pkName = Field('pkName', String, default='', nullable=False, comment='包名')
abs = Field('abs', Bool, default=False, nullable=False, comment='是否是抽象类')
dApi = Field('dApi', Bool, default=False, nullable=False, comment='是否是dubbo接口类')
dInf = Field('dInf', String, default='', nullable=False, comment='dubbo接口interface值')
hPrefix = Field('hPrefix', String, default='', nullable=False, comment='http接口路径前')
faces = Field('faces', String, default='', nullable=False, comment='被我实现的接口(多个";"分割)')
impls = Field('impls', String, default='', nullable=False, comment='实现我的接口(多个";"分割)')
p = Field('p', String, default='', nullable=False, comment='我继承的父类')
subs = Field('subs', String, default='', nullable=False, comment='继承我的子类(多个";"分割)')
sLine = Field('sLine', Int, default=0, nullable=False, comment='开始行(从1开始)')
eLine = Field('eLine', Int, default=0, nullable=False, comment='结束行')
zsScan = Field('zsScan', String, default='', nullable=False, comment='资损字段')
class JClassClassEdge(Edge):Java类->类-边类型
classJClassClassEdge(Edge):
"""
Java类->类-边类型
"""
__NAME_PREFIX__ = 'ejcc_'
FACE_RELATION = 'face'
PARENT_RELATION = 'parent'
src_vid = Vid('src_vid', String, nullable=False, comment='起始点ID')
dst_vid = Vid('dst_vid', String, nullable=False, comment='目的点ID')
relation = Field('relation', String, default='', nullable=False, comment='类->类关系(face: 我实现的接口, parent: 我的父类)')
class JMethodMethodEdge(Edge):Java方法->方法-边类型
classJMethodMethodEdge(Edge):
"""
Java方法->方法-边类型
"""
__NAME_PREFIX__ = 'ejmm_'
CALL_RELATION = 'call'
REWRITE_RELATION = 'rewrite'
HEAVY_LOD_RELATION = 'heavy_load'
src_vid = Vid('src_vid', String, nullable=False, comment='起始点ID')
dst_vid = Vid('dst_vid', String, nullable=False, comment='目的点ID')
relation = Field('relation', String, default='', nullable=False, comment='方法->方法关系(call: 调用, rewrite: 重写,heavy_load:重载)')
api = Field('api', Bool, default=False, nullable=False, comment='src(开始)点是否是接口')
class JMethodClassEdge(Edge):Java方法->类-边类型
classJMethodClassEdge(Edge):
"""
Java方法->类-边类型
"""
__NAME_PREFIX__ = 'ejmc_'
MYC_RELATION = 'myc'
src_vid = Vid('src_vid', String, nullable=False, comment='起始点ID')
dst_vid = Vid('dst_vid', String, nullable=False, comment='目的点ID')
relation = Field('relation', String, default='', nullable=False, comment='方法->类关系(myc: 我的类)')
class JChangeClassMethodEdge(Edge):Java变更(类->方法)-边类型
classJChangeClassMethodEdge(Edge):
"""
Java变更(类->方法)-边类型
- 推荐接口任务产生的增量方法和类
"""
__NAME_PREFIX__ = 'ejccm_'
src_vid = Vid('src_vid', String, nullable=False, comment='起始点ID')
dst_vid = Vid('dst_vid', String, nullable=False, comment='目的点ID')
contrast_v = Field('contrast_v', String, default='', nullable=False, comment='对比版本(commit)')
调用链展示
使用 NebulaGraph 获取调用链信息
获取到变更类下面的方法
(先获取目标点ID,再根据点ID查询点数据性能最佳),查询语句如下:
MATCH (v:{tag_name}) WHERE v.{tag_name}.cSign IN [{cSignsStr}] RETURN id(v);
MATCH (v:{tag_name}) WHERE id(v) IN [{tag_name_vid_str}] RETURN v;
使用点 ID 根据“JMethodMethodEdge 边”遍历找出调用链中是接口的入口方法
这里使用 GO 语句,并设置 1 到 50 跳来遍历(即从原始点开始找 50 层关系,如:a() -> b() -> c(),则a()到b()是 1 跳,b()到c()是 1 跳,a()到c() 是 2 跳
GO 1 TO 50 STEPS FROM {vid_str} OVER {edge_name} REVERSELY WHERE properties(edge).api == true YIELD DISTINCT src(edge);
查找增量类和增量方法
通过“JChangeClassMethodEdge 边”的属性“contrast_v”的值来判断哪些是目标增量边,根据增量边的起始点 ID 和结束点 ID 来获取最终的增量方法和增量类
MATCH (v_c:{c_tag_name})-[e:{ccme_name}]->(v_m:{m_tag_name}) RETURN e, v_m;
如:获取增量变更方法
defget_add_java_method(self, **kwargs) -> ResponseResult:
"""获取增量变更方法"""
app_name = kwargs.get('app_name')
current_version = kwargs.get('current_version')
contrast_version = kwargs.get('contrast_version')
zs_scan = kwargs.get('zs_scan')
...
tag_handler = TagHandler(app_name, current_version)
c_tag_name = tag_handler.get_tag_name(JClassTag)
m_tag_name = tag_handler.get_tag_name(JMethodTag)
edge_handler = EdgeHandler(app_name, current_version)
ccme_name = edge_handler.get_edge_name(JChangeClassMethodEdge)
# 模版 MATCH (v_c:tag_name1)-[e:edge_name]->(v_m:tag_name2) RETURN e, v_m;
n_gql = f"MATCH (v_c:{c_tag_name})-[e:{ccme_name}]->(v_m:{m_tag_name}) RETURN e, v_m;"
_res = edge_handler.session.execute_json(n_gql)
if type(_res) is bytes:
_res = _res.decode(encoding='utf-8')
# Nebula Graph Studio前端客户端工具报【-1009:SemanticError: PlanNode(Start) not support to append an input.】错是客
# 户端版本不匹配,代码里可以支持
res: dict = json.loads(_res)
if res['errors'][0]['code'] != 0:
err = f"{res['errors'][0]['code']}:{res['errors'][0].get('message')}"
return ResponseResult.fail_response(message='获取增量变更方法失败', err=err)
# 筛选&组装数据
raws = res['results'][0]['data']
res_class_zs = []
res_method_rows = []
class_zs_dict = {}
for raw in raws:
_inner_row = raw['row']
# 对比版本commitID只取前8位
if _inner_row[0]['contrast_v'][:8] == contrast_version[:8]:
_new_row = {
"meta": [{
"type": "vertex",
"id": raw['meta'][1]['id']
}],
"row": [_inner_row[1]]
}
res_method_rows.append(_new_row)
method_tags: List[JMethodTag] = tag_handler.convert_raw_to_tag_model(res_method_rows, JMethodTag)
# 获取类的资损标签
if zs_scan and method_tags:
cSign_set = set()
# 获取增量类
for method_tag in method_tags:
_fsr_cSign = String.fsr(method_tag.cSign)
if _fsr_cSign notin cSign_set:
cSign_set.add(_fsr_cSign)
n_gql = f'MATCH (v:{c_tag_name}) ' \
f'WHERE v.{c_tag_name}.zsScan != "" AND v.{c_tag_name}.sign IN [{",".join(cSign_set)}] ' \
f'RETURN v.{c_tag_name}.sign, v.{c_tag_name}.zsScan;'
_res = edge_handler.session.execute_json(n_gql)
if type(_res) is bytes:
_res = _res.decode(encoding='utf-8')
# 户端版本不匹配,代码里可以支持
res: dict = json.loads(_res)
if res['errors'][0]['code'] != 0:
err = f"{res['errors'][0]['code']}:{res['errors'][0].get('message')}"
return ResponseResult.fail_response(message='获取增量变更方法失败', err=err)
raws = res['results'][0]['data']
for raw in raws:
_row = raw['row']
class_zs_dict[_row[0]] = {
'class_sign': _row[0],
'zs': _row[1]
}
class_infos = []
class_tag_info_dict = {}
class_n = 0
for method_tag in method_tags:
class_tag_info = class_tag_info_dict.get(method_tag.cSign)
if class_tag_info isNone:
class_tag_info = class_tag_info_dict[method_tag.cSign] = {
'class_sign': method_tag.cSign,
'class_zs': ''if class_zs_dict.get(method_tag.cSign) isNoneelse class_zs_dict.get(method_tag.cSign)['zs'],
'methods': []
}
class_infos.append(class_tag_info)
class_n += 1
# 转化成JSON
_method_tag = method_tag.__dict__.copy()
_calls = _method_tag['calls']
_method_tag['calls'] = _calls.split(';') if _calls else []
_usages = _method_tag['usages']
_method_tag['usages'] = _usages.split(';') if _usages else []
class_tag_info['methods'].append(_method_tag)
return ResponseResult.success_response(data={'class_n': class_n, 'method_n': len(method_tags),
'class_infos': class_infos})
四、总结与展望
精准测试是一套有效提高软件测试质量和效率的技术体系,可以有效解决传统测试中的盲测、漏测、冗测等现象提升测试效率和准确性,前置暴露风险,保障上线质量。
精准测试从理念走向落地的关键,在于能否构建一条高效、可靠的“数据驱动链”,而 NebulaGraph,正是锻造这条链条的核心引擎 。凭借其卓越存储与关联查询能力,NebulaGraph 将原本散落、静态的代码方法,编织成一张动态、立体的调用关系网。
未来,我们期待能与 NebulaGraph 开源社区更紧密地协同共建,将得物在复杂业务场景下的实战经验反哺社区,共同推动图技术的创新与应用。
本文发布于「NebulaGraph 公众平台」,更多产品资讯请访问「NebulaGraph 官网」
本文转自「得物技术」公众号⬇️
推荐阅读
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
LiteAPI:轻量高效的 Java 低代码 API 开发框架
Lite API 是一个基于JFinal框架的轻量级 API 敏捷开发框架。通过约定优于配置的方式,实现统一的标准,让您用尽可能简单的方式完成尽可能多的需求。告别 CRUD,拒绝重复劳动,远离搬砖。 Lite API 借鉴了Magic-API的优秀设计理念,结合 JFinal 框架的高性能特性,为开发者提供了一套简洁高效的 API 开发解决方案。 特性 零编码开发:无需定义Controller、Service、Dao、Model等 Java 对象即可完成常见的 HTTP API 接口开发 基于 JFinal:基于 JFinal 5.x 框架,继承其高性能、简洁开发的特点 可视化界面:提供 UI 界面测试 API 接口 多数据库支持:支持 MySQL、MariaDB、Oracle、DB2、PostgreSQL、SQLServer 等支持 JDBC 规范的数据库 动态脚本:基于 Magic-Script 的动态编译技术,无需重启,即时生效 多数据源:支持多数据源配置,支持在线配置数据源 分页查询:支持分页查询以及自定义分页查询 SQL 缓存:支持 SQL 缓存,以及自定义 SQL 缓存 在...
-
下一篇
云原生进化论:加速构建 AI 应用
作者:杨秋弟 大家好,我是来自阿里云智能集团的资深产品专家杨秋弟,今天很荣幸能在云栖大会的场合,和大家分享过去一年我们在支持企业构建 AI 应用过程的一些实践和思考。 智能体应用已成为应用架构中的重要组成部分 从事 AI 领域的同仁,无论是科研学者,落地 AI 的企业,还是提供 AI 技术或产品的供应端企业,我想大家都有非常一致的体感,就是 AI 应用的发展是势不可挡的,并正在重塑软件行业,我们来看几组数据: 模型调用增速爆发: GenAI 的支出增长尤其迅猛,预计将从 2023 年的 160 亿美元增长到 2027 年的 1430 亿美元,年均复合增长率(CAGR)高达 73.3%。 新增应用智能化比例高: 到 2027 年,实现人工智能与 6 大重点领域广泛深度融合,新一代智能终端、智能体等应用普及率超 70%。 Agentic AI 逐步进入企业核心系统: 到 2028 年,33% 的企业软件将集成代理型 AI,而 2024 年这一比例还不到 1%。 由此看来,智能体应用已经逐步成为客户应用架构中的重要组成部分。这个演进过程,应用的发展和基础设施的升级是双向驱动、相互成就的。 应...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS8编译安装MySQL8.0.19
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7设置SWAP分区,小内存服务器的救世主
- MySQL8.0.19开启GTID主从同步CentOS8
- MySQL数据库中FOR UPDATE的使用
- Windows10,CentOS7,CentOS8安装Nodejs环境
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程








微信收款码
支付宝收款码