接口自動化測試框架-httprunner V2.x中文使用手冊-快速上手

本文將通過一個簡單的示例來展示 HttpRunner 的核心功能使用方法。

案例介紹

該案例作爲被測服務,主要有兩類接口:

  • 權限校驗,獲取 token
  • 支持 CRUD 操作的 RESTful APIs,所有接口的請求頭域中都必須包含有效的 token

案例的實現形式爲 flask 應用服務(api_server.py),啓動方式如下:

 

$ export FLASK_APP=docs/data/api_server.py
$ export FLASK_ENV=development
$ flask run
 * Serving Flask app "docs/data/api_server.py" (lazy loading)
 * Environment: development
 * Debug mode: on
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 989-476-348

服務啓動成功後,我們就可以開始對其進行測試了。

測試準備

抓包分析

在開始測試之前,我們需要先了解接口的請求和響應細節,而最佳的方式就是採用 Charles Proxy 或者 Fiddler 這類網絡抓包工具進行抓包分析。

例如,在本案例中,我們先進行權限校驗,然後成功創建一個用戶,對應的網絡抓包內容如下圖所示:

 

 

 

通過抓包,我們可以看到具體的接口信息,包括請求的URL、Method、headers、參數和響應內容等內容,基於這些信息,我們就可以開始編寫測試用例了。

生成測試用例

爲了簡化測試用例的編寫工作,HttpRunner 實現了測試用例生成的功能。

首先,需要將抓取得到的數據包導出爲 HAR 格式的文件,假設導出的文件名稱爲 demo-quickstart.har

導出方法:

 

然後,在命令行終端中運行如下命令,即可將 demo-quickstart.har 轉換爲 HttpRunner 的測試用例文件。

 

$ har2case docs/data/demo-quickstart.har -2y
INFO:root:Start to generate testcase.
INFO:root:dump testcase to YAML format.
INFO:root:Generate YAML testcase successfully: docs/data/demo-quickstart.yml

使用 har2case 轉換腳本時默認轉換爲 JSON 格式,加上 -2y 參數後轉換爲 YAML 格式。兩種格式完全等價,YAML 格式更簡潔,JSON 格式支持的工具更豐富,大家可根據個人喜好進行選擇。關於 har2case 的詳細使用說明,請查看《錄製生成測試用例》

經過轉換,在源 demo-quickstart.har 文件的同級目錄下生成了相同文件名稱的 YAML 格式測試用例文件 demo-quickstart.yml,其內容如下:

 

- config:
    name: testcase description
    variables: {}

- test:
    name: /api/get-token
    request:
        headers:
            Content-Type: application/json
            User-Agent: python-requests/2.18.4
            app_version: 2.8.6
            device_sn: FwgRiO7CNA50DSU
            os_platform: ios
        json:
            sign: 9c0c7e51c91ae963c833a4ccbab8d683c4a90c98
        method: POST
        url: http://127.0.0.1:5000/api/get-token
    validate:
        - eq: [status_code, 200]
        - eq: [headers.Content-Type, application/json]
        - eq: [content.success, true]
        - eq: [content.token, baNLX1zhFYP11Seb]

- test:
    name: /api/users/1000
    request:
        headers:
            Content-Type: application/json
            User-Agent: python-requests/2.18.4
            device_sn: FwgRiO7CNA50DSU
            token: baNLX1zhFYP11Seb
        json:
            name: user1
            password: '123456'
        method: POST
        url: http://127.0.0.1:5000/api/users/1000
    validate:
        - eq: [status_code, 201]
        - eq: [headers.Content-Type, application/json]
        - eq: [content.success, true]
        - eq: [content.msg, user created successfully.]

現在我們只需要知道如下幾點:

  • 每個 YAML/JSON 文件對應一個測試用例(testcase)
  • 每個測試用例爲一個list of dict結構,其中可能包含全局配置項(config)和若干個測試步驟(test)
  • config 爲全局配置項,作用域爲整個測試用例
  • test 對應單個測試步驟,作用域僅限於本身

如上便是 HttpRunner 測試用例的基本結構。

關於測試用例的更多內容,請查看《測試用例結構描述》

首次運行測試用例

測試用例就緒後,我們可以開始調試運行了。

爲了演示測試用例文件的迭代優化過程,我們先將 demo-quickstart.json 重命名爲 demo-quickstart-0.json(對應的 YAML 格式:demo-quickstart-0.yml)。

運行測試用例的命令爲hrun,後面直接指定測試用例文件的路徑即可。

 

$ hrun docs/data/demo-quickstart-0.yml
INFO     Start to run testcase: testcase description
/api/get-token
INFO     POST http://127.0.0.1:5000/api/get-token
INFO     status_code: 200, response_time(ms): 9.26 ms, response_length: 46 bytes

ERROR    validate: content.token equals baNLX1zhFYP11Seb(str)   ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)
ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/get-token
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'app_version': '2.8.6', 'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios'}
json: {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'}
verify: True

====== response details ======
status_code: 200
headers: {'Content-Type': 'application/json', 'Content-Length': '46', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
body: '{"success": true, "token": "tXGuSQgOCVXcltkz"}'

F
/api/users/1000
INFO     POST http://127.0.0.1:5000/api/users/1000
ERROR    403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
ERROR    validate: status_code equals 201(int)  ==> fail
403(int) equals 201(int)
ERROR    validate: content.success equals True(bool)    ==> fail
False(bool) equals True(bool)
ERROR    validate: content.msg equals user created successfully.(str)   ==> fail
Authorization failed!(str) equals user created successfully.(str)
ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/users/1000
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
json: {'name': 'user1', 'password': '123456'}
verify: True

====== response details ======
status_code: 403
headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:43:55 GMT'}
body: '{"success": false, "msg": "Authorization failed!"}'

F

======================================================================
FAIL: test_0000_000 (httprunner.api.TestSequense)
/api/get-token
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
    test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: content.token equals baNLX1zhFYP11Seb(str)   ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
    self.fail(str(ex))
AssertionError: validate: content.token equals baNLX1zhFYP11Seb(str)    ==> fail
tXGuSQgOCVXcltkz(str) equals baNLX1zhFYP11Seb(str)

======================================================================
FAIL: test_0001_000 (httprunner.api.TestSequense)
/api/users/1000
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
    test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int)  ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str)    ==> fail
Authorization failed!(str) equals user created successfully.(str)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
    self.fail(str(ex))
AssertionError: validate: status_code equals 201(int)   ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str)    ==> fail
Authorization failed!(str) equals user created successfully.(str)

----------------------------------------------------------------------
Ran 2 tests in 0.026s

FAILED (failures=2)
INFO     Start to render Html report ...
INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513835.html

非常不幸,兩個接口的測試用例均運行失敗了。

優化測試用例

從兩個測試步驟的報錯信息和堆棧信息(Traceback)可以看出,第一個步驟失敗的原因是獲取的 token 與預期值不一致,第二個步驟失敗的原因是請求權限校驗失敗(403)。

接下來我們將逐步進行進行優化。

調整校驗器

默認情況下,har2case 生成用例時,若 HTTP 請求的響應內容爲 JSON 格式,則會將第一層級中的所有key-value轉換爲 validator。

例如上面的第一個測試步驟,生成的 validator 爲:

 

"validate": [
    {"eq": ["status_code", 200]},
    {"eq": ["headers.Content-Type", "application/json"]},
    {"eq": ["content.success", true]},
    {"eq": ["content.token", "baNLX1zhFYP11Seb"]}
]

運行測試用例時,就會對上面的各個項進行校驗。

問題在於,請求/api/get-token接口時,每次生成的 token 都會是不同的,因此將生成的 token 作爲校驗項的話,校驗自然就無法通過了。

正確的做法是,在測試步驟的 validate 中應該去掉這類動態變化的值。

去除該項後,將用例另存爲 demo-quickstart-1.json(對應的 YAML 格式:demo-quickstart-1.yml)。

再次運行測試用例,運行結果如下:

 

$ hrun docs/data/demo-quickstart-1.yml
INFO     Start to run testcase: testcase description
/api/get-token
INFO     POST http://127.0.0.1:5000/api/get-token
INFO     status_code: 200, response_time(ms): 6.61 ms, response_length: 46 bytes

.
/api/users/1000
INFO     POST http://127.0.0.1:5000/api/users/1000
ERROR    403 Client Error: FORBIDDEN for url: http://127.0.0.1:5000/api/users/1000
ERROR    validate: status_code equals 201(int)  ==> fail
403(int) equals 201(int)
ERROR    validate: content.success equals True(bool)    ==> fail
False(bool) equals True(bool)
ERROR    validate: content.msg equals user created successfully.(str)   ==> fail
Authorization failed!(str) equals user created successfully.(str)
ERROR    ******************************** DETAILED REQUEST & RESPONSE ********************************
====== request details ======
url: http://127.0.0.1:5000/api/users/1000
method: POST
headers: {'Content-Type': 'application/json', 'User-Agent': 'python-requests/2.18.4', 'device_sn': 'FwgRiO7CNA50DSU', 'token': 'baNLX1zhFYP11Seb'}
json: {'name': 'user1', 'password': '123456'}
verify: True

====== response details ======
status_code: 403
headers: {'Content-Type': 'application/json', 'Content-Length': '50', 'Server': 'Werkzeug/0.14.1 Python/3.7.0', 'Date': 'Sat, 26 Jan 2019 14:45:34 GMT'}
body: '{"success": false, "msg": "Authorization failed!"}'

F

======================================================================
FAIL: test_0001_000 (httprunner.api.TestSequense)
/api/users/1000
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 54, in test
    test_runner.run_test(test_dict)
httprunner.exceptions.ValidationFailure: validate: status_code equals 201(int)  ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str)    ==> fail
Authorization failed!(str) equals user created successfully.(str)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/debugtalk/.pyenv/versions/3.6-dev/lib/python3.6/site-packages/httprunner/api.py", line 56, in test
    self.fail(str(ex))
AssertionError: validate: status_code equals 201(int)   ==> fail
403(int) equals 201(int)
validate: content.success equals True(bool) ==> fail
False(bool) equals True(bool)
validate: content.msg equals user created successfully.(str)    ==> fail
Authorization failed!(str) equals user created successfully.(str)

----------------------------------------------------------------------
Ran 2 tests in 0.018s

FAILED (failures=1)
INFO     Start to render Html report ...
INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548513934.html

經過修改,第一個測試步驟已經運行成功了,第二個步驟仍然運行失敗(403),還是因爲權限校驗的原因。

參數關聯

我們繼續查看 demo-quickstart-1.json,會發現第二個測試步驟的請求 headers 中的 token 仍然是硬編碼的,即抓包時獲取到的值。在我們再次運行測試用例時,這個 token 已經失效了,所以會出現 403 權限校驗失敗的問題。

正確的做法是,我們應該在每次運行測試用例的時候,先動態獲取到第一個測試步驟中的 token,然後在後續測試步驟的請求中使用前面獲取到的 token。

在 HttpRunner 中,支持參數提取(extract)和參數引用的功能($var)。

在測試步驟(test)中,若需要從響應結果中提取參數,則可使用 extract 關鍵字。extract 的列表中可指定一個或多個需要提取的參數。

在提取參數時,當 HTTP 的請求響應結果爲 JSON 格式,則可以採用.運算符的方式,逐級往下獲取到參數值;響應結果的整體內容引用方式爲 content 或者 body。

例如,第一個接口/api/get-token的響應結果爲:

 

{"success": true, "token": "ZQkYhbaQ6q8UFFNE"}

那麼要獲取到 token 參數,就可以使用 content.token 的方式;具體的寫法如下:

 

"extract": [
  {"token": "content.token"}
]

其中,token 作爲提取後的參數名稱,可以在後續使用 $token 進行引用。

 

"headers": {
  "device_sn": "FwgRiO7CNA50DSU",
  "token": "$token",
  "Content-Type": "application/json"
}

修改後的測試用例另存爲 demo-quickstart-2.json(對應的 YAML 格式:demo-quickstart-2.yml)。

再次運行測試用例,運行結果如下:

 

$ hrun docs/data/demo-quickstart-2.yml
INFO     Start to run testcase: testcase description
/api/get-token
INFO     POST http://127.0.0.1:5000/api/get-token
INFO     status_code: 200, response_time(ms): 8.32 ms, response_length: 46 bytes

.
/api/users/1000
INFO     POST http://127.0.0.1:5000/api/users/1000
INFO     status_code: 201, response_time(ms): 3.02 ms, response_length: 54 bytes

.

----------------------------------------------------------------------
Ran 2 tests in 0.019s

OK
INFO     Start to render Html report ...
INFO     Generated Html report: /Users/debugtalk/MyProjects/HttpRunner-dev/httprunner-docs-v2x/reports/1548514191.html

經過修改,第二個測試步驟也運行成功了。

base_url

雖然測試步驟運行都成功了,但是仍然有繼續優化的地方。

繼續查看 demo-quickstart-2.json,我們會發現在每個測試步驟的 URL 中,都採用的是完整的描述(host+path),但大多數情況下同一個用例中的 host 都是相同的,區別僅在於 path 部分。

因此,我們可以將各個測試步驟(test) URL 的 base_url 抽取出來,放到全局配置模塊(config)中,在測試步驟中的 URL 只保留 PATH 部分。

 

- config:
    name: testcase description
    base_url: http://127.0.0.1:5000

- test:
    name: get token
    request:
        url: /api/get-token

調整後的測試用例另存爲 demo-quickstart-3.json(對應的 YAML 格式:demo-quickstart-3.yml)。

重啓 flask 應用服務後再次運行測試用例,所有的測試步驟仍然運行成功。

變量的申明和引用

繼續查看 demo-quickstart-3.json,我們會發現測試用例中存在較多硬編碼的參數,例如 app_version、device_sn、os_platform、user_id 等。

大多數情況下,我們可以不用修改這些硬編碼的參數,測試用例也能正常運行。但是爲了更好地維護測試用例,例如同一個參數值在測試步驟中出現多次,那麼比較好的做法是,將這些參數定義爲變量,然後在需要參數的地方進行引用。

在 HttpRunner 中,支持變量申明(variables)和引用($var)的機制。在 config 和 test 中均可以通過 variables 關鍵字定義變量,然後在測試步驟中可以通過 $ + 變量名稱 的方式引用變量。區別在於,在 config 中定義的變量爲全局的,整個測試用例(testcase)的所有地方均可以引用;在 test 中定義的變量作用域僅侷限於當前測試步驟(teststep)。

對上述各個測試步驟中硬編碼的參數進行變量申明和引用調整後,新的測試用例另存爲 demo-quickstart-4.json(對應的 YAML 格式:demo-quickstart-4.yml)。

重啓 flask 應用服務後再次運行測試用例,所有的測試步驟仍然運行成功。

抽取公共變量

查看 demo-quickstart-4.json 可以看出,兩個測試步驟中都定義了 device_sn。針對這類公共的參數,我們可以將其統一定義在 config 的 variables 中,在測試步驟中就不用再重複定義。

 

- config:
    name: testcase description
    base_url: http://127.0.0.1:5000
    variables:
        device_sn: FwgRiO7CNA50DSU

調整後的測試用例見 demo-quickstart-5.json(對應的 YAML 格式:demo-quickstart-5.yml)。

實現動態運算邏輯

在 demo-quickstart-5.yml 中,參數 device_sn 代表的是設備的 SN 編碼,雖然採用硬編碼的方式暫時不影響測試用例的運行,但這與真實的用戶場景不大相符。

假設 device_sn 的格式爲 15 長度的字符串,那麼我們就可以在每次運行測試用例的時候,針對 device_sn 生成一個 15 位長度的隨機字符串。與此同時,sign 字段是根據 headers 中的各個字段拼接後生成得到的 MD5 值,因此在 device_sn 變動後,sign 也應該重新進行計算,否則就會再次出現簽名校驗失敗的問題。

然而,HttpRunner 的測試用例都是採用 YAML/JSON 格式進行描述的,在文本格式中如何執行代碼運算呢?

HttpRunner 的實現方式爲,支持熱加載的插件機制(debugtalk.py),可以在 YAML/JSON 中調用 Python 函數。

具體地做法,我們可以在測試用例文件的同級或其父級目錄中創建一個 debugtalk.py 文件,然後在其中定義相關的函數和變量。

例如,針對 device_sn 的隨機字符串生成功能,我們可以定義一個 gen_random_string 函數;針對 sign 的簽名算法,我們可以定義一個 get_sign 函數。

 

import hashlib
import hmac
import random
import string

SECRET_KEY = "DebugTalk"

def gen_random_string(str_len):
    random_char_list = []
    for _ in range(str_len):
        random_char = random.choice(string.ascii_letters + string.digits)
        random_char_list.append(random_char)

    random_string = ''.join(random_char_list)
    return random_string

def get_sign(*args):
    content = ''.join(args).encode('ascii')
    sign_key = SECRET_KEY.encode('ascii')
    sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
    return sign

然後,我們在 YAML/JSON 測試用例文件中,就可以對定義的函數進行調用,對定義的變量進行引用了。引用變量的方式仍然與前面講的一樣,採用$ + 變量名稱的方式;調用函數的方式爲${func($var)}

例如,生成 15 位長度的隨機字符串並賦值給 device_sn 的代碼爲:

 

"variables": [
  {"device_sn": "${gen_random_string(15)}"}
]

使用 $user_agent、$device_sn、$os_platform、$app_version 根據簽名算法生成 sign 值的代碼爲:

 

"json": {
  "sign": "${get_sign($user_agent, $device_sn, $os_platform, $app_version)}"
}

對測試用例進行上述調整後,另存爲 demo-quickstart-6.json(對應的 YAML 格式:demo-quickstart-6.yml)。

重啓 flask 應用服務後再次運行測試用例,所有的測試步驟仍然運行成功。

參數化數據驅動

請確保你使用的 HttpRunner 版本號不低於 2.0.0

在 demo-quickstart-6.yml 中,user_id 仍然是寫死的值,假如我們需要創建 user_id 爲 1001~1004 的用戶,那我們只能不斷地去修改 user_id,然後運行測試用例,重複操作 4 次?或者我們在測試用例文件中將創建用戶的 test 複製 4 份,然後在每一份裏面分別使用不同的 user_id ?

很顯然,不管是採用上述哪種方式,都會很繁瑣,並且也無法應對靈活多變的測試需求。

針對這類需求,HttpRunner 支持參數化數據驅動的功能。

在 HttpRunner 中,若要採用數據驅動的方式來運行測試用例,需要創建一個文件,對測試用例進行引用,並使用 parameters 關鍵字定義參數並指定數據源取值方式。

例如,我們需要在創建用戶的接口中對 user_id 進行參數化,參數化列表爲 1001~1004,並且取值方式爲順序取值,那麼最簡單的描述方式就是直接指定參數列表。具體的編寫方式爲,新建一個測試場景文件 demo-quickstart-7.yml(對應的 JSON 格式:demo-quickstart-7.json),內容如下所示:

 

config:
    name: testcase description

testcases:
    create user:
        testcase: demo-quickstart-6.yml
        parameters:
            user_id: [1001, 1002, 1003, 1004]

僅需如上配置,針對 user_id 的參數化數據驅動就完成了。

重啓 flask 應用服務後再次運行測試用例,測試用例運行情況如下所示:

點擊查看運行日誌

可以看出,測試用例總共運行了 4 次,並且每次運行時都是採用的不同 user_id。

關於參數化數據驅動,這裏只描述了最簡單的場景和使用方式,如需瞭解更多,請進一步閱讀《數據驅動使用手冊》

查看測試報告

在每次使用 hrun 命令運行測試用例後,均會生成一份 HTML 格式的測試報告。報告文件位於 reports 目錄下,文件名稱爲測試用例的開始運行時間。

例如,在運行完 demo-quickstart-1.json 後,將生成如下形式的測試報告:

 

關於測試報告的詳細內容,請查看《測試報告》部分。

 

 

 

總結

到此爲止,HttpRunner 的核心功能就介紹完了,掌握本文中的功能特性,足以幫助你應對日常項目工作中至少 80% 的自動化測試需求。

當然,HttpRunner 不止於此,如需挖掘 HttpRunner 的更多特性,實現更復雜場景的自動化測試需求,可繼續閱讀後續文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章