基於 BDD 理論的 Nebula 集成測試框架重構(下篇)

本文首發於 Nebula Graph 公衆號 NebulaGraphCommunity,Follow 看大廠圖數據庫技術實踐。python

基於 BDD 理論的 Nebula 集成測試框架重構(下篇)

上篇文章中,咱們介紹了 Nebula Graph 的集成測試的演進過程。本篇就介紹一下向測試集合中添加一個用例,併成功運行全部的測試用例的過程。ios

環境準備

在構建 2.0 測試框架之初,咱們定製了部分工具類來幫助測試框架快速地啓停一個單節點的 nebula 服務,其中有檢查端口衝突、修改部分配置選項等功能。原來的執行流程以下:git

  1. 經過 python 腳本啓動 nebula 的服務;
  2. 調用pytest.main併發執行全部的測試用例;
  3. 中止 nebula 的服務。

其中的不便之處在於,當須要給 pytest 指定某些參數選項時,須要將該參數透傳給pytest.main函數,而且每次運行單個測試用例須要經過cmake生成的腳原本操做,不是很方便。咱們但願「測試用例在哪兒,就在哪兒執行測試」。github

服務啓動

在本次測試框架的改造過程當中,咱們除了改變了程序入口以外,大部分複用了原來封裝好的邏輯。因爲 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 語句。這樣作,存在以下問題:markdown

  1. 測試數據集大的狀況,INSERT 語句會變得冗長,client 執行超時;
  2. 不易拓展新的測試數據集,須要將現成的 csv 數據文件構形成對應的 nGQL 語句文件;
  3. 不能複用相同的數據集,好比但願同一份 csv 導入到不一樣 VID 類型的 space 中測試,須要構造不一樣的 INSERT 語句。

針對以上的問題,參考nebula-importer的實現,咱們將導入的邏輯和數據集徹底分離,從新實現了 python 版的導入模塊。不過,目前只支持導入 csv 類型的數據文件,且每一個 csv 文件中只能存儲一個tag/edge類型。session

重構導入的邏輯以後,目前 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 報名傳送門

相關文章
相關標籤/搜索