詳解 Sqllogictest
寫作背景
之前的文章《如何為 Databend 添加新的測試》介紹了 Databend 如何進行測試,其中 SQL 的測試方法中提到了 sqllogictest,大家對這種新引入的測試方法比較感興趣,但當前介紹這個的中文資料很少,因此我們整理下近期的一些工作和思考,跟大家分享一下 sqllogictest 的設計、實現(xiàn)及應用。
關于 sqllogictest
數(shù)據(jù)庫質量保證
測試維度和測試覆蓋率是保證數(shù)據(jù)庫質量的關鍵,測試維度包括 單元測試、模糊測試、功能測試(sqllogictest 在這里)、端到端(e2e)測試、性能測試等。數(shù)據(jù)庫功能測試方案核心是通過執(zhí)行 SQL 語句獲得返回值,將返回值與預期進行對比,通常存在幾個需要考慮的問題:
如何設計用例的格式?
如何比對結果?多數(shù)方案直接保存結果文件,無法區(qū)分具體 SQL 的執(zhí)行結果,只能通過在用例之間增加輸出的方式,導致用例不直觀;
不同客戶端或者數(shù)據(jù)庫的差異如何解決?如不同的客戶端對返回內容格式化方式有差異,不同的數(shù)據(jù)庫對某些類型輸出有差異;
發(fā)展簡介
sqllogictest 最早是 SQLite 進行測試的工具,由 SQLite 的作者 D. Richard Hipp(理查德 希普)設計開發(fā)。關于相關的設計理念可以在?https://www.sqlite.org/sqllogictest/doc/trunk/about.wiki?找到。
sqllogictest 的目標是保證數(shù)據(jù)庫引擎執(zhí)行結果是正確的。因此它不會關注其他方面的問題,諸如性能、索引優(yōu)化、磁盤內存的使用情況、并發(fā)和鎖等。
目前主流的數(shù)據(jù)庫都有自己的 sqllogictest 測試工具和測試用例,測試用例的語法略有差異并且不能互相兼容,測試工具的實現(xiàn)方式也有所區(qū)別:
YDB【1】?使用 python 實現(xiàn)
CockroachDB【2】?使用 go 實現(xiàn)
Databend 為何引入 sqllogictest
Databend 原來有一套功能測試工具,借鑒 clickhouse 的測試方法,將功能測試用例分為 stateless 測試和 stateful 測試。通過 Databend-test(python 實現(xiàn))來執(zhí)行,用例通過腳本的方式編寫(或者一個 SQL 文件),用例的預期結果寫成同名不同后綴名的文件并將兩者的輸出進行 diff 對比。如果相同則認為結果正確。這種測試方法對錯誤用例的編寫和修改不友好外,此外 Databend 支持多套不同的 handler(如 mysql、http、clickhouse)這些 handler 都有被測試的需求,有點像測試不同的數(shù)據(jù)庫。但原來的測試方法沒辦法解決這個問題,因此我們開始尋找一種能解決這些問題的測試方法和工具。
Databend 如何實現(xiàn) sqllogictest
雖然都叫 sqllogictest,但實現(xiàn)差異很大,這種差異不僅在用例語法的支持上,實現(xiàn)使用的技術棧及整個工具的實現(xiàn)程度區(qū)別也很大。導致不管是測試集還是工具本身,很難開箱即用。經(jīng)過對不同實現(xiàn)方案的分析對比,我們發(fā)現(xiàn) sqllogictest 的核心功能需求不多、整個開源社區(qū)實現(xiàn)分裂無法滿意的直接用、本身隨著測試工作的推進越來越多的需求會加入進來導致大量的定制化開發(fā)。最終我們選擇使用 python 自己造輪子。

sqllogictest 包含多個不同的 Runner 負責與不同的數(shù)據(jù)庫或者 handler 交互,每個 Runner 要實現(xiàn)基類 SuiteRunner 中的方法,包括
execute_ok
execute_error
execute_query
batch_execute
這些方法是執(zhí)行 sqllogictest 的核心,除此之外 SuiteRunner 類還會保存執(zhí)行過程中的一些狀態(tài)和控制變量。

以 Httprunner 的實現(xiàn)為例,實現(xiàn)了必要的接口 execute_ok 、execute_error、execute_query、batch_execute,除此之外還有兩個函數(shù)? get_connection 和 reset_connection 主要用來重置連接和會話。

通過 Statement 類去解析用例文件,目前沒有考慮實現(xiàn)一個解釋器的方案,而采用簡單的逐行讀取文件通過正則匹配的方式實現(xiàn)語法解析。這么做的好處是可以快速實現(xiàn);缺點是后續(xù)要添加語法支持比較麻煩。通過 LogicError 來輸出錯誤信息,包含錯誤出現(xiàn)的 runner 名稱、錯誤的消息(包含出錯的 statement 的詳情)及錯誤的類型。此外還實現(xiàn)了一個 LogicTestStatistics 類,記錄每一個 SQL 執(zhí)行的時間開銷,最終輸出的統(tǒng)計信息還比較簡單,后續(xù)可以補充完善。
如何編寫 sqllogictest
基礎功能
可以通過這個實例快速入門:?https://github.com/datafuselabs/databend/blob/main/tests/logictest/suites/select_0?當前支持的執(zhí)行器: mysql handler, http handler, clickhouse handler。支持注釋語法 ,使用 -- 來注釋特定的行。statement類型:
ok
語句正確執(zhí)行,無錯誤返回
error
語句執(zhí)行錯誤,且返回的錯誤信息包含指定預期的內容,通常使用返回碼,也可以使用消息文本(但不直觀)
query
B Boolean? ? ? ? ? ? ?布爾類型
T text ? ? ? ? ? ? ? ? 文本類型
F floating point? ? ?浮點類型
I integer ? ? ? ? ? ? ? 整形
語句執(zhí)行返回帶有結果集, 通過 options 和 labels 區(qū)分結果集的對比方式
options由字符組成,每個字符代表結果集中的一個列,支持的字符有:
labels 不同的數(shù)據(jù)庫(handler)對結果的處理存在差異通過 labels 區(qū)分開,對于存在多個差異的,通過逗號分隔開
相對而言 ok 和 error 比較好理解,query 相對復雜一些,以下是一個 query 類型用例的示例(僅供參考不代表實際結果):
測試流程控制語法
1.支持 skipif ?用于跳過指定的 runner
2.支持 onlyif 用于僅執(zhí)行指定的 runner
3.如果遇到一些偶發(fā)的測試失敗,無法短期解決的??梢酝ㄟ^ skipped 跳過這個用例,也可以選擇注釋掉。
執(zhí)行輸出
成功樣例:
當前的 summary 中包含了對測試執(zhí)行過程的簡單統(tǒng)計,包括執(zhí)行的用例文件數(shù)、每個用例文件包含多少個語句、每個語句執(zhí)行的平均時間及用例執(zhí)行的平均時間。
失敗樣例 1:
可以看出失敗的用例為 base\15_query\alias\having_with_alias.test 中的第四行 ,返回的內容預期為 1 但實際是空。
失敗樣例 2:
可以看出失敗的用例為 base\02_function\02_0017_function_strings_oct 的第一行,返回的錯誤為表已存在。以上示例中我們發(fā)現(xiàn)從輸出內容很容易就可以定位到具體的用例文件甚至哪一行哪個 SQL,對于需要對比結果的,也會把結果的預期和實際返回值打印出來,輕松的找出錯誤的問題。極大的改善了開發(fā)人員的使用體驗,提升了排查問題的效率。
在流水線中使用 sqllogictest
當提交一個 PR(Pull Request)到 Databend 倉庫時,會觸發(fā)一系列的流水線;當構建部分完成后,會進入測試的部分。流水線會將構建產(chǎn)物在一個全新的環(huán)境上運行起來,同時執(zhí)行各項測試,sqllogictest 是其中的一個重要環(huán)節(jié)。如圖:

只有當所有的測試都通過后,該提交才能合并到主干,保證了每次修訂不會影響功能預期,而我們需要做的就是完善用例、提示用例的覆蓋率。
運行 sqllogictest
貢獻者:
直接在克隆 Databend 代碼后,在 Databend 目錄內執(zhí)行 make sqllogic-test
使用者:
1.部署并運行 Databend,參考?https://databend.rs/doc/deploy/deploying-databend
2.拷貝與運行版本一致的 Databend 代碼,進入 tests/logictest 目錄
3.安裝 python3(>=3.8)
4.安裝 python3 依賴,通過目錄下的 requirements.txt
5.執(zhí)行 python3 main.py
運行參數(shù)
命令行參數(shù):
--suites other_dir 將會運行 ./other_dir 下的用例文件
--run-dir ydb 將會運行 ./suites/ 下的目錄名包含 ydb 的目錄內的用例
--skip-dir ydb 將會跳過?./suites/ 下的目錄名包含 ydb 的目錄內的用例
python main.py "03_0001" 指定執(zhí)行名稱中包含 03_0001 的用例
環(huán)境變量參數(shù):

這些參數(shù)可以滿足個性化的運行條件,比如不在本地部署的 Databend 或者測試 mysql、clickhouse(僅支持 http,不支持 clickhouse native 協(xié)議)
注意:由于 SQL 方言問題,我們的用例可能存在其他數(shù)據(jù)庫不支持的語句,其他數(shù)據(jù)庫的用例也存在類似情況。
編寫技巧
不在意結果的用例使用 statement ok
statement error 盡量使用錯誤碼,message 是不穩(wěn)定的
statement query 的結果集里的空格僅用于區(qū)分不同的列,寫多個空格除了影響外觀外,不會影響測試結果
statement query 對于返回結果中有空行的,需要用 /t tab 鍵占位
由于放棄了在用例里支持排序和重試語法(移到了測試工具中實現(xiàn)),必要時帶上 order by 保證結果順序始終一致
用例文件如何組織
測試套件的來源為第一層目錄,如當前我們有 base、ydb 兩部分套件;base 是自有用例、ydb 是從 ydb 引入的用例。在套件內的目錄組織暫時還沒形成明確的規(guī)范,通常以下組織方式:
根據(jù)語句來區(qū)分如 cockroachdb 的用例組織
根據(jù)語句類型或者設計到的模塊來區(qū)分 如 DML、 DDL 或者? planner_v2,跟隨功能開發(fā)走
拓展考慮
支持返回列的正則匹配,主要需求為當前 statement query 只支持精確匹配,無法滿足部分模糊匹配的需求:匹配時間格式,這樣就支持一些不返回固定時間的用例
后續(xù)計劃
完善 sqllogictest 的使用體驗及工具鏈
sqllogictest 的使用體驗包括功能型的需求的完善、日志輸出更加友好、用例遷移工具(從 SQL 文件或者第三方 sqllogictest 用例文件)等。
完善測試用例及覆蓋率
各家測試數(shù)據(jù)集是寶貴的財富,往往是花費大量時間去設計和完善的,遷移用例為我所用對于加速測試覆蓋率意義重大。同時我們也要完善自身的測試場景和功能的測試覆蓋率。
Open-SQQL-Logictest
這是個暢想,重復造輪子并不是一個好習慣,除非輪子能造的更簡單、更好用。如果有一天,對于sqllogictest的各方需求能整理清楚,定義出標準,這也許會成為可能。
參考資料
https://www.sqlite.org/sqllogictest/doc/trunk/about.wikihttps://github.com/datafuselabs/databend/tree/main/tests/logictest
引用鏈接
[1]
?YDB:?https://github.com/ydb-platform/ydb/tree/main/ydb/tests/functional/suite_tests?
[2]
?CockroachDB:?*https://github.com/cockroachdb/cockroach/tree/master/pkg/sql/logictest
關于 Databend
Databend 是一款開源、彈性、低成本,基于對象存儲也可以做實時分析的新式數(shù)倉。期待您的關注,一起探索云原生數(shù)倉解決方案,打造新一代開源 Data Cloud。
Databend 文檔:https://databend.rs/
Twitter:https://twitter.com/Datafuse_Labs
Slack:https://datafusecloud.slack.com/
Wechat:Databend
GitHub :https://github.com/datafuselabs/databend
