本文首发于 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. 2024了,我不想再用AOP收集业务操作日志了 | 京东云技术团队

    0.背景 在近期的项目中,系统涉及到针对系统的业务操作日志统计功能,由于本系统位于业务链路的中心环节,负责接收上游系统的数据,并将基于用户操作产生的数据传递至下游系统,鉴于业务链路的复杂性和操作场景的 ...

  2. 【团队效率提升】Python-PyWebIO介绍

    作者:京东零售 关键 Q&A快速了解PyWebIO Q:首先,什么是PyWebIO? A:PyWebIO提供了一系列命令式的交互函数,能够让咱们用只用Python就可以编写 Web 应用, 不 ...

  3. js加减乘除运算出现精度丢失

    做乘法运算出现精度丢失 let aa= 2106.49 console.log( aa*10000 ) //21064899.999999996 console.log( Math.round(aa* ...

  4. .NET 6 使用 System.Drawing.Common 出现 The type initializer for ‘Gdip’ threw an exception 异常的解决办法

    出现问题的原因 在Linux环境部署.NET Core程序时,如果要到System.Drawing.Common引用会出现该问题,目前大量的第三方组件使用该Windows专用库,尤其是涉及图片处理.W ...

  5. 4.3 Windows驱动开发:监控进程与线程对象操作

    在内核中,可以使用ObRegisterCallbacks这个内核回调函数来实现监控进程和线程对象操作.通过注册一个OB_CALLBACK_REGISTRATION回调结构体,可以指定所需的回调函数和回 ...

  6. PE结构:VA&RVA&FOA 转换复习

    复习一下,不然会忘 1.imagebase 映像基地址 ,默认是0x400000 2.va 虚拟地址,载入OD后的地址,已经映射到内存的地址. 计算实际装入地址 VA imagebase (映像基址) ...

  7. Flask 框架:运用WTForms实现用户注册

    WTForms 是用于web开发的灵活的表单验证和呈现库,它可以与您选择的任何web框架和模板引擎一起工作,并支持数据验证.CSRF保护.国际化等,运用WTForms框架并配合Flask可实现一个带有 ...

  8. 从嘉手札<2024-1-17>

    昨天我以为 人生是一场体验 是一辆不会回头的列车 我们遇到了风景 感悟了风景 放下了风景 构成了自己 今天我以为 静水流深.光而不耀 可多思必多疑 思维是一种极为复杂的东西 我曾经觉得知行合一是对自我 ...

  9. Star 4.2k,这是我用过最舒服的跨平台Redis桌面客户端

    项目介绍 Tiny RDM 一个现代化轻量级的跨平台Redis桌面客户端,支持Mac.Windows和Linux 软件截图 运行效果 版本展示 配置连接 项目亮点 极致轻量 极小包体,随处安装随处使用 ...

  10. 使用Visual studio code 进行.NET 开发

    Visual studio code 作为一款强大的编辑器,相信很多开发者都用过.vs code 的强大源自开源生态丰富,编辑器本身简单,但是加上各式的插件,就变得无比牛逼,基本可以替代现有的大部分工 ...