本文首发于 Nebula Graph 公众号 NebulaGraphCommunity,Follow 看大厂图数据库技术实践。

上篇文章中,我们介绍了 Nebula Graph 的集成测试的演进过程。本篇就介绍一下向测试集合中添加一个用例,并成功运行所有的测试用例的过程。

环境准备

在构建 2.0 测试框架之初,我们定制了部分工具类来帮助测试框架快速地启停一个单节点的 nebula 服务,其中有检查端口冲突、修改部分配置选项等功能。原来的执行流程如下:

  1. 通过 python 脚本启动 nebula 的服务;
  2. 调用pytest.main并发执行所有的测试用例;
  3. 停止 nebula 的服务。

其中的不便之处在于,当需要给 pytest 指定某些参数选项时,需要将该参数透传给pytest.main函数,并且每次运行单个测试用例需要通过cmake生成的脚本来操作,不是很方便。我们希望“测试用例在哪儿,就在哪儿执行测试”。

服务启动

在本次测试框架的改造过程中,我们除了改变了程序入口之外,大部分复用了原来封装好的逻辑。由于 nebula 目前积累了很多的用例,单进程运行已经不能满足快速迭代的需求,在尝试了其他并行插件之后,考虑到兼容性,我们最终选择了 pytest-xdist 插件来加速整个测试流程。

但是 pytest 只提供了四种 scope 的 fixture:session,module,class 和 function。而我们希望能用一种 global 级别的 fixture 来完成 nebula 服务的启动和初始化。目前最高层次的 session 级别还是每个 runner 都要执行一次,如此如果有 8 个 runner 的话,就要启动 8 个 nebula 服务,这不是我们期望的。

参考 pytest-xdist 的文档,需要通过文件锁来进行不同 runner 之间的并行控制。为了让控制逻辑足够的简单,我们把程序启停和预备的逻辑同执行测试的过程分开,使用单独的步骤控制 nebula 的启动,当某些测试有问题时,还可以通过 nebula-console 单独连接测试的服务,进行进一步的验证调试。

数据导入

在此之前,Nebula 的数据导入过程是直接执行一条拼接好的 nGQL INSERT 语句。这样做,存在如下问题:

  1. 测试数据集大的情况,INSERT 语句会变得冗长,client 执行超时;
  2. 不易拓展新的测试数据集,需要将现成的 csv 数据文件构造成对应的 nGQL 语句文件;
  3. 不能复用相同的数据集,比如希望同一份 csv 导入到不同 VID 类型的 space 中测试,需要构造不同的 INSERT 语句。

针对以上的问题,参考nebula-importer的实现,我们将导入的逻辑和数据集完全分离,重新实现了 python 版的导入模块。不过,目前只支持导入 csv 类型的数据文件,且每个 csv 文件中只能存储一个tag/edge类型。

重构导入的逻辑之后,目前 nebula 的测试数据集变得清晰明了:

nebula-graph/tests/data
├── basketballplayer
│ ├── bachelor.csv
│ ├── config.yaml
│ ├── like.csv
│ ├── player.csv
│ ├── serve.csv
│ ├── team.csv
│ └── teammate.csv
├── basketballplayer_int_vid
│ └── config.yaml
└── student
├── config.yaml
├── is_colleagues.csv
├── is_friend.csv
├── is_schoolmate.csv
├── is_teacher.csv
├── person.csv
├── student.csv
└── teacher.csv 3 directories, 16 files

每个目录包含一个 space 中所有的 csv 数据文件,通过该目录下的config.yaml来配置每个文件的描述以及 space 的详细信息。通过这份配置信息,我们也实现了 basketballplayer 和 basketballplayer_int_vid两个 space 共享同一份数据。以后如果想添加新的测试数据集,只要增加一个类似 basketballplayer 的数据目录即可。config.yaml的具体内容见repo

安装依赖

除却常用的 pytest 和 nebula-python 库之外,目前的测试框架还用到了 pytest-bdd 和 pytest-xdist 等插件。此外,为了更好地统一添加测试用例 feature 文件的格式,我们引入了社区的reformat-gherkin工具,并基于此做了部分格式的调整,来保持与 openCypher TCK feature 文件的格式统一。

目前 nebula-python 和 reformat-gherkin 两款插件都是通过源码直接安装,我们在nebula-graph/tests下提供了Makefile来简化用户的操作流程。执行测试的所有环境准备只需要执行命令:

$ cd nebula-graph/tests && make init-all

我们也将上述格式检查集成到了 GitHub Action 的 CI 流程中,如果用户修改的测试文件格式不合预期,可通过make fmt命令做本地的格式化处理。

编写用例

由上篇所述,现在 nebula 的集成测试变为“黑盒”测试,用户不再需要关心自己编写的语句怎么调用,调用什么函数比较符合预期结果。只要按照约定的规范,使用近似“自然语言”的方式在 feature 文件中描述自己的用例即可。以下是一个测试用例的示例:

Feature: Variable length pattern match (m to n)
Scenario: both direction expand with properties
Given a graph with space named "basketballplayer"
When executing query:
"""
MATCH (:player{name:"Tim Duncan"})-[e:like*2..3{likeness: 90}]-(v)
RETURN e, v
"""
Then the result should be, in any order, with relax comparison:
| e | v |
| [[:like "Tim Duncan"<-"Manu Ginobili"], [:like "Manu Ginobili"<-"Tiago Splitter"]] | ("Tiago Splitter") |

Given提供测试的初始条件,这里初始化一个名为 "basketballplayer" 的 space。When描述测试的输入,即 nGQL 语句。Then给出期望结果和期望比较的方式,这里表示无序宽松比较表格中的结果。

Feature 文件结构

Feature 文件是 Gherkin 语言描述的一种文件格式,主要由如下几个部分构成:

  • Feature:可以添加当前文件的“Title”,也可以详细描述文件内容;
  • Background:后续 Scenario 共同使用的步骤;
  • Scenario:由一个个步骤描述每个测试用例的场景;
  • Examples:可以进一步将测试场景和测试数据进行分离,简化当前 Feature 文件中 Scenarios 的书写;

每个 Scenario 又分为了不同的 step,每个 step 都有特殊的意义:

  • Given: 设置当前测试场景的初始条件,上述 Background 中只能含有 Given 类型的 step;
  • When: 给定当前测试场景的输入;
  • Then: 描述做完 When 的 step 后期望得到的结果;
  • And: 可以紧跟上述 Given/When/Then 中任何一种类型的 step,进一步补充上述的 step 的动作;
  • Examples: 类似上述 Examples 描述,不过作用的范围限定在单个 Scenario 中,不影响同 Feature 文件中的其他 Scenario 测试。

Steps

由上面描述可知,Scenario 就是有一个个的 step 组成,nebula 在兼容 openCypher TCK 的 step 基础上又定制了一些特有的步骤来方便测试用例的书写:

  1. Given a graph with space named "basketballplayer":使用预先导入 “basketballplayer” 数据的 space;
  2. creating a new space with following options:创建一个含有如下参数的新的 space,可以指定 name、partition_num、replica_factor、vid_type、charset 和 collate 等参数;
  3. load "basketballplayer" csv data to a new space:向新 space 导入 “basketballplayer” 数据集;
  4. profiling query:对 query 语句执行PROFILE,返回结果中会含有 execution plan;
  5. wait 3 seconds:等待 3 秒钟,在 schema 相关的操作时往往需要一定的数据同步时间,这时就可以用到该步骤;
  6. define some list variables:定义一些变量表示元素很多的 List 类型,方便在期望结果中书写对应的 List;
  7. the result should be, in any order, with relax comparison:执行结果进行无序宽松比较,意味着期望结果中用户写了什么就比较什么,没写的部分即使返回结果中有也不作比较;
  8. the result should contain:返回结果必须包含期望结果;
  9. the execution plan should be:比较返回结果中的执行计划。

除却以上的这些步骤,还可根据需要定义更多的 steps 来加速测试用例的开发。

Parser

根据TCK 的描述可知,openCypher 定义了一组图语义的表示方式来表达期望的返回结果。这些点边的格式借鉴了MATCH查询中的 pattern,所以如果熟悉 openCypher 的查询,基本可以很容易理解 TCK 测试场景中的结果。比如部分图语义的格式如下所示:

  1. 点的描述:(:L {p:1, q:"string"});
  2. 边的描述:[:T {p:0, q:"string"}];
  3. 路径的描述:<(:L)-[:T]->(:L2)>

但是 Nebula Graph 同 Neo4J 的在图模型上还是有一些不同,比如在 Nebula Graph 中每个 Tag 均可以有自己的属性,那么按照现有的表述方式是不能描述含有多个带属性 Tag 的 vertex 的。在边的表示上也有差异,Nebula Graph 的 Edge Key 是由四元组组成<src, type, rank, dst>,而现有的表示也不能描述边的 src、dst 和 rank 的值。故而在考虑了这些差异之后,我们扩充了现有 TCK 的 expected results 表达:

  1. 点的描述支持带属性的多个 Tag:("VID" :T1{p:0} :T2{q: "string"})
  2. 边的描述支持 src、dst 和 rank 的表示:[:type "src"->"dst"@rank {p:0, q:"string"}]
  3. 路径就是循环上述点边的表示即可,同 TCK 。

通过上述的点边描述方式上的扩充,即兼容 TCK 现有用例,又契合了 Nebula Graph 的设计。在解决了表达方式上的问题后,面临的下一个问题是如何高效无误地转化上述的表示到具体的数据结构,以便能够跟真正的查询结果做比较。在考虑了正则匹配、parser 解析等方案后,我们选择构造一个解析器的方式来处理这些具有特定语法规则的字符串,这样做的好处有如下的几点:

  1. 可以根据具体的语法规则让解析出来的 AST 符合查询返回结果的数据结构,两者再进行比较时,便是具体结构中的具体字段的校验了;
  2. 避免处理复杂的正则匹配字符串,减少解析的错误;
  3. 可以支持其他字符串解析的需求,比如正则表达式、列表、集合等

借助ply.yacc 和 ply.lex 两个 library,我们可以用少量的代码实现上述复杂的需求,具体实现见nbv.py 文件

测试流程

目前的测试流程变为:

1) 编写 Feature 文件

目前 Nebula Graph 所有的 feature 用例均位于 github.com/vesoft-inc/nebula-graph repo 中的tests/tck/features目录中。

2) 启动 nebula graph 服务

$ cd /path/to/nebula-graph/tests
$ make up # 启动 nebula graph 服务

3) 本地执行测试

$ make fmt # 格式化
$ make tck # 执行 TCK 测试

4)停止 nebula graph 服务

$ mak
e down

调试

当编写的用例需要调试时,便可以使用 pytest 支持的方式来进一步的调试,比如重新运行上次过程中失败的用例:

$ pytest --last-failed tck/ # 运行 tck 目录中上次执行失败的用例
$ pytest -k "match" tck/ # 执行含有 match 字段的用例

也可以在 feature 文件中对特定的一个 scenario 打上一个 mark,只运行该被 mark 的用例,比如:

# in feature file
@testmark
Scenario: both direction expand with properties
Given a graph with space named "basketballplayer"
... # in nebula-graph/tests directory
$ pytest -m "testmark" tck/ # 运行带有 testmark 标记的测试用例

总结

站在前人的肩膀之上才让我们找到更适合 Nebula Graph 的测试方案,在此也一并感谢文中提到的所有开源工具和项目。

在实践 pytest-bdd 的过程中,也发现其中一些不完美的地方,比如其跟 pytest-xdist 等插件兼容性的问题(gherkin-reporter),还有 pytest 没有原生提供 global scope 级别的 fixture 等。但终究其带给 Nebula Graph 的收益要远大于这些困难。

上篇中有提到不需要用户进行编程,并非凭空想象,当我们把上述的模式固定后,可以开发一套添加测试用例的脚手架,让用户在页面上进行数据“填空”,自动生成对应的 feature 测试文件,如此便可进一步地方便用户,此处可以留给感兴趣的社区用户来做些尝试了。

交流图数据库技术?加入 Nebula 交流群请先填写下你的 Nebula 名片,Nebula 小助手会拉你进群~~

想要和其他大厂交流图数据库技术吗?NUC 2021 大会等你来交流:NUC 2021 报名传送门

基于 BDD 理论的 Nebula 集成测试框架重构(下篇)的更多相关文章

  1. 基于 BDD 理论的 Nebula 集成测试框架重构(上篇)

    本文首发于 Nebula Graph 公众号 NebulaGraphCommunity,Follow 看大厂图数据库技术实践. 测试框架的演进 截止目前为止,在 Nebula Graph 的开发过程中 ...

  2. 基于特定领域国土GIS应用框架设计及应用

              基于特定领域国土GIS应用框架 设计及应用              何仕国 2012年8月16日   摘要: 本文首先讲述了什么是框架和特定领域框架,以及与国土GIS 这个特定领 ...

  3. C++反射机制:可变参数模板实现C++反射(使用C++11的新特性--可变模版参数,只根据类的名字(字符串)创建类的实例。在Nebula高性能网络框架中大量应用)

    1. 概要   本文描述一个通过C++可变参数模板实现C++反射机制的方法.该方法非常实用,在Nebula高性能网络框架中大量应用,实现了非常强大的动态加载动态创建功能.Nebula框架在码云的仓库地 ...

  4. 基于CPU版本的Caffe推理框架

    最近一段时间,认真研究了一下caffe.但是,里面内容过多,集合了CPU版本和GPU版本的代码,导致阅读起来有些复杂.因此,特意对caffe代码进行了重构,搭建一个基于CPU版本的Caffe推理框架. ...

  5. 网络框架重构之路plain2.0(c++23 without module) 综述

    最近互联网行业一片哀叹,这是受到三年影响的后遗症,许多的公司也未能挺过寒冬,一些外资也开始撤出市场,因此许多的IT从业人员加入失业的行列,而且由于公司较少导致许多人求职进度缓慢,很不幸本人也是其中之一 ...

  6. 网络框架重构之路plain2.0(c++23 without module) 环境

    接下来本来就直接打算分享框架重构的具体环节,但重构的代码其实并没有完成太多,许多的实现细节在我心中还没有形成一个定型.由于最近回归岗位后,新的开发环境需要自己搭建,搭建的时间来说花了我整整一天的时间才 ...

  7. 转-基于NodeJS的14款Web框架

    基于NodeJS的14款Web框架 2014-10-16 23:28 作者: NodeJSNet 来源: 本站 浏览: 1,399 次阅读 我要评论暂无评论 字号: 大 中 小 摘要: 在几年的时间里 ...

  8. 第三篇 基于.net搭建热插拔式web框架(重造Controller)

    由于.net MVC 的controller 依赖于HttpContext,而我们在上一篇中的沙箱模式已经把一次http请求转换为反射调用,并且http上下文不支持跨域,所以我们要重造一个contro ...

  9. 第二篇 基于.net搭建热插拔式web框架(沙箱的构建)

    上周五写了一个实现原理篇,在评论中看到有朋友也遇到了我的问题,真的是有种他乡遇知己的感觉,整个系列我一定会坚持写完,并在最后把代码开源到git中.上一篇文章很多人看了以后,都表示不解,觉得不知道我到底 ...

  10. 基于.net搭建热插拔式web框架(实现原理)

    第一节:我们为什么需要一个热插拔式的web框架? 模块之间独立开发 假设我们要做一个后台管理系统,其中包括“用户活跃度”.“产品管理”."账单管理"等模块.每个模块中有自己的业务特 ...

随机推荐

  1. 如何去掉 node.js 获取MySQL数据产生的RowDataPacket

    如何去掉 node.js 获取MySQL数据产生的RowDataPacket 利用JSON.stringify()把对象转为对象字符串,可去掉RowDataPacket. router.post('/ ...

  2. PHP伪协议与文件包含漏洞

    https://www.cnblogs.com/weak-chicken/p/12275806.html 1 file:// - 访问本地文件系统 2 http:// - 访问 HTTP(s) 网址 ...

  3. uni-app 实现下拉刷新功能

    我们在运用uni-app开发小程序或h5时,常常需要页面实现下拉刷新功能. 在 js 中定义 onPullDownRefresh 处理函数(和onLoad等生命周期函数同级),监听该页面用户下拉刷新事 ...

  4. 4款超好用的AI换脸软件,一键视频直播换脸(附下载链接)

    随着AIGC的火爆,AI换脸技术也被广泛应用于娱乐.广告.电影制作等领域,本期文章系统介绍了市面上超火的4款AI软件 换脸整合包收录了全部4款AI工具,请按照需要选择下载: 百度网盘:https:// ...

  5. 安装和定位vimrc

    在上一篇文章中,我们简单开了一个头,阐述了下学习vim的必要性,这章开始,会慢慢由浅入深的学习它的一套完整的,高效的文本编辑方式方法.废话不多说,咱们正式开始吧 安装NeoVim 相对于vim来说,n ...

  6. net8来了

    11 月 15 日开始的为期三天的 .NET Conf 在线活动的开幕日上,.NET 8作为微软的开源跨平台开发平台正式发布..NET 团队着重强调云.性能.全栈 Blazor.AI 和 .NET M ...

  7. Xcode常用环境变量与常见使用场景

    在Xcode的工程配置中,与路径相关的都是使用环境变量,这样可以避免使用决定路径时项目移植性差的问题. Xcode常用宏 __FILE__ 当前文件所在目录 __DATE__ 编译日期的字符串,格式为 ...

  8. Ubuntu 23.04 正式发布

    Ubuntu 23.04 "Lunar Lobster" 是 Ubuntu 操作系统的最新短期支持版本,该版本将获得 9 个月的支持,直到 2024 年 1 月.如果你需要长期支持 ...

  9. 联想T30瘦客户机安装DoraOS体验

    硬件配置:J4125 .8G RAM. 128G ROM 联想T30台式电脑,它是一台迷你计算机,尺寸小巧玲珑,重量适中,方便携带.它的性能十分强大,能够运行各种应用程序,包括网页浏览器.视频播放器等 ...

  10. 练习(java):关于自增运算的练习

    //练习3: byte bb1 = 127; bb1++; System.out.println("bb1 = " + bb1);//-128 bb1--; System.out. ...