七牛HTTP測試工具包:Qiniu httptest package

jopen 9年前發布 | 27K 次閱讀 測試工具 httptest.v1
Qiniu httptest package - 七牛HTTP測試工具包

687474703a2f2f6f70656e2e71696e6975646e2e636f6d2f6c6f676f2e706e67.jpg

下載

go get -u qiniupkg.com/httptest.v1

概述

一些背景資料:

這是一套 HTTP 服務測試腳本框架及實用程序。我們定義了一個測試腳本的 DSL 語言。大體看起來是這樣的:

#為了讓一套代碼同時可以測試 Stage 環境和 Product 環境,我們推薦將 Host、AK/SK 作為環境變量傳入
#同時也避免了 AK/SK 這樣敏感內容進入代碼庫
match $(env) `envdecode QiniuTestEnv`
auth qboxtest `qbox $(env.AK) $(env.SK)`
host rs.qiniu.com $(env.RSHost)

post http://rs.qiniu.com/delete/`base64 Bucket:Key`
auth qboxtest
#發起請求,并開始檢查結果
ret  200

post http://rs.qiniu.com/batch
auth qboxtest
match $(ekey1) |base64 Bucket:Key|
match $(ekey2) |base64 Bucket2:Key2|
json '{
    "op": ["/delete/$(ekey1)", "/delete/$(ekey2)"]
}'
#發起請求,并開始檢查結果
ret  200

post http://rs.qiniu.com/batch
auth qboxtest
form op=/delete/`base64 Bucket:Key`&op=/delete/`base64 Bucket:NotExistKey`
#發起請求,并開始檢查結果
ret  298
json '[{"code": $(code1)}, {"code": 612}]'
match $(code1) 200

命令詳解

文法

整體是以命令行文法為基礎。一條指令由命令及命令參數構成。命令及命令參數之間以空白字符(空格或TAB)分隔。如果某個參數中包含空格或其他特殊字符,則可以:

  • 用 \ 轉義。比如 '\ ' 表示 ' '(空格),'\t' 表示 TAB 字符,等等。
  • 用 '...' 或 "..." 包含。兩者都允許出現 $(var) 或 ${var} 形式表示的變量(這一點和 linux shell 有很大不同,詳細見后文 “智能變量” 一節)。他們的區別在于:'...' 中不支持用 \ 轉義,也不支持子命令(見后文 “子命令” 一節),出現任何內容都當作普通字符對待。所以'\t|abc|'用 "..." 來表達必須用"\\t\|abc\|"。

類型系統

在 linux shell 的命令行中,所有的輸入輸出都是字符串,基本上沒有類型系統可言。這一點我們和 linux shell 有很大的不同。我們的腳本有完備的類型系統。

我們支持并僅支持 json 文法所支持的所有類型。基礎類型包括:number (在 Go 語言中是 float64)、bool、string。復合類型包括 array(在 Go 語言里面是 slice,不是數組)和 dictionary/object (在 Go 語言中是 map/interface{})。特別需要注意的是,我們的類型系統里面沒有 int 類型。null 不是空 array,也不是空 dictionary,而是空 object。

由于我們采用了命令行文法,所以表達類型和常規文法有一定的差異。比如 200、'200'、"200" 都表示同一個東西:number 類型的 200。表達字符串 "200" 必須用 '"200"' 或者 "\"200\""。

上面樣例中的

'[{"code": $(code1)}, {"code": 612}]'

是一個 array,如果我們用緊湊文法寫,避免任何的空白字符,可以寫成這樣:

[{"code":$(code1)},{"code":612}]

當然我們建議表達復合類型的時候,盡量還是用 '...' 來寫,以保證可閱讀性。

另外,考慮到 json 只有如下這些語法單元:

  • array/dictionary/object/string:[...],{...},null,"..."
  • bool:true,false
  • number:0..9,-
  • var:$(...)// 我們擴展的語法

我們可以增加一條規則:

  • 所有 a..z 或 A..Z 開頭的非true,false,null文本,被認為是合法的 json string。

也就是說,以下這段文本:

http://rs.qiniu.com/batch

等價于:

'"http://rs.qiniu.com/batch"'

另外,對于那些明確接受 string 參數的指令,也可以省略 '"..."' 這樣的外衣。

智能變量

和 linux shell 類似,我們也支持 $(var) 或 ${var} 格式的變量。但是,$(var) 并不像 linux shell 那樣,在命令行詞法分析階段就被處理掉了,它是本 DSL 代表變元的語法成分,和 "..." 是常字符串的語法成分類似。另外,由于我們存在類型系統,所以 $(var) 表達的不是一段文本,而是一個可能是任意類型的 object。這帶來這樣一些差異:

  • 支持 dictionary/object、array 的 member 成員獲取操作。比如對 dictionary 可以做 $(a.b.c) 形式的 member 訪問。對于 array,理論上應該支持 $(a[1]) 這種形式,不過目前我們用的是 $(a.1)。表達a[2].b[3].c可以用 $(a.2.b.3.c) 表示。
  • 變量智能 marshal。在不同的場景下,變量的 marshal 結果會有差異。所以變量 marshal 需要上下文,而不是簡單的字符串替換。比如 http://rs.qiniu.com/delete/$(ekey) 和 {"delete":$(ekey)} 這兩個地方,$(ekey) 的 marshal 結果有很大的差別。除了出現在 json 里面的 $(ekey) 需要用 "..." 括起來,而 url 中不需要這樣的顯著差別外,對于特殊字符的 escape 轉義方法也完全不同(但是這個細節經常容易被忽略)。

匹配(match)

這幾乎是這套 DSL 中最核心的概念。作為一門語言,有變量,自然會有賦值的概念。在這里的確有實現賦值的能力,但它不叫賦值,而是叫匹配。先看例子:

match $(a.b) 1
match $(a.c) '"hello"'

這個例子的結果是,得到了一個變量 a,其值為 {"b": 1, "c": "hello"}。

到現在為止,你看到的 match 像賦值的一面。但是你不能對已經綁定了特定值的變量再次賦不同的值:

match $(a.b) 1
match $(a.b) 1 #可以匹配,因為$(a.b)的值的確為1 match $(a.b) 2 #失敗,1和2不相等

match 語句可以很復雜,如:

match '{"c": {"d": $(d)}}' '{"c": {"d": "hello", "e": "world"}, "f": 1}'

一般地,match 命令的文法為:

match <ExpectedObject> <SourceObject>

其中<SourceObject>中不能出現未綁定的變量。<ExpectedObject>中則允許存在未綁定的變量。<ExpectedObject>和<SourceObject>不必完全一致,但是<ExpectedObject>中出現的,在<SourceObject>中也必須出現,也就是要求是子集關系(<ExpectedObject>是<SourceObject>的子集)。<ExpectedObject>中某個變量如果還未綁定,則按照對應的<SourceObject>的值進行綁定;如果變量已經綁定,則兩邊的值必須是匹配的。

支撐我們整個 DSL 的基石,正是匹配文法。這里你可以把所有支持的命令都看成是 bool 表達式,如果返回 true 則成功,返回 false 則失敗。我們看下一開始你看到的例子的片段:

ret 298
json '[{"code": $(code1)}, {"code": 612}]'

它表達的含義是,要求返回包的 StatusCode = 298,然后返回的 Response Body 必須能夠匹配'[{"code": $(code1)}, {"code": 612}]',Content-Type 則必須為application/json。它等價于:

ret #不帶參數的 ret 僅僅發起請求,并將返回包存儲在 $(resp) 變量中,不做任何匹配 match 298 $(resp.code) match '["application/json"]' $(resp.header.Content-Type) match '[{"code": $(code1)}, {"code": 612}]' $(resp.body)

子命令

如同 linux shell 一樣,我們可以在一條命令中,嵌入另一個命令,并把該命令的執行結果作為本命令輸入的一部分。這種嵌入其他命令之中的命令,我們稱為子命令。樣例如下:

host rs.qiniu.com `env QiniuRSHost` match $(ekey1) |base64 Bucket:Key| match $(ekey2) |base64 Bucket2:Key2|

和 linux shell 相比,我們多了一個子命令語法:|...|。這沒有別的意圖,純粹是為了 Go 語言的友好性(linux 風格的子命令在 Go 里面表達需要特別費勁)。

我們樣例中的兩個子命令env和base64都是返回 string 類型。但作為我們 DSL 的一部分,子命令同樣可以返回我們類型系統中的任意類型。所以,原則上我們的子命令如同變量一樣,有著上下文相關的 marshal 需求,比如:

post http://rs.qiniu.com/delete/`base64 Bucket:Key` match $(foo) {"ekey":`base64 Bucket:Key`}

為了達到這樣的效果,我們可以想象一種子命令的實現手法:

match $(__auto_var_1) `base64 Bucket:Key` post http://rs.qiniu.com/delete/$(__auto_var_1) match $(__auto_var_2) `base64 Bucket:Key` match $(foo) {"ekey":$(__auto_var_2)}

也就是為每個子命令背地里生成一個自動變量,這樣就可以讓上下文相關的 marshal 能力,統一到由 “智能變量” 來支持。

HTTP API 測試

請求包:

req <Method> <Url> #可以簡寫為 post <Url> 或 get <Url> 或 delete <Url> auth <AuthInterface> header <Key1> <Value1> header <Key2> <Value2> body <BodyType> <Body> #可以簡寫為 form <FormBody> 或 json <JsonBody>

返回包測試:

ret <Code> #參數可不指定。不帶參數的 ret 僅僅發起請求,并將返回包存儲在 $(resp) 變量中 header <Key1> <Value1> header <Key2> <Value2> body <BodyType> <Body> #可以簡寫為 json <JsonBody>

多案例支持

一般測試案例框架都有選擇性執行某個案例、多個案例共享 setUp、tearDown 這樣的啟動和終止代碼。我們 DSL 也支持,如下:

#代碼片段1
...

case testCase1
#代碼片段2
...

case testCase2
#代碼片段3
...

tearDown
#代碼片段4
...

這段代碼里面,“代碼片段1” 將被認為是 setUp 代碼,“代碼片段4” 是 tearDown 代碼,所有 testCase 開始前都會執行一遍“代碼片段1”,退出前執行一遍“代碼片段4”。每個 case 不用寫 end 語句,遇到下一個 case 或者遇到 tearDown 就代表該 case 結束。

運算能力

目前,這套 DSL 的運算能力是比較有限的。基本上只能做字符串拼接(concat)。如下:

match $(c) '"Hello $(a), $(b)!"'

如果我們希望做復雜運算,我設想未來有可能通過支持calc這樣的子命令。例如:

match $(g) `calc max($(a), $(b), $(c)) + sin($(d)) + $(e)`

實現一個calc并不復雜,在 C++ 中用 TPL 只是幾十分鐘的事情(但在 Go 語言里面怎么做還沒有特別去研究)。

考慮盡可能利用現有資源的話,我們可以考慮內嵌 lua 來實現 calc 支持。比如:

match $(g) `calc math.max($(a), $(b), $(c)) + 
math.sin($(d)) + $(e)`

參考:

有了calc事情就更有意思了,我們還可以直接用calc命令做斷言,比如:

calc $(a) < $(b)

在calc外面不套任何指令,由于calc返回false或true,而基于前面返回true表示成功,返回false表示失敗的原則,這個指令直接就是斷言。當然為了友好,我們可以搞個別的名字:

assert $(a) < $(b)

流程控制

等等,難道我們真要做一個圖靈完備的語言?上面的運算能力的討論已經有點脫離需求了(先實際使用中檢驗吧),我們就此打住吧。

項目主頁:http://www.baiduhome.net/lib/view/home/1437556636021

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!