#安裝
pip3.7 install httprunner
hrun -V 查看版本,能看到版本信息,就代表安裝成功
hrun -h 查看幫助信息
#命令說明
在 HttpRunner 安裝成功后,系統中會新增 4 個命令:
httprunner : 核心命令
hrun:httprunner 的縮寫,功能與 httprunner 完全相同
hmake:httprunner make的別名,用於將YAML/JSON測試用例轉換為pytest文件
har2case:輔助工具,可將標准通用的 HAR 格式(HTTP Archive)轉換為YAML/JSON格式的測試用例
#查看命令說明
httprunner startproject -h
#新建項目
httprunner startproject httprunner_demo
#查看項目結構
macname@MacdeMacBook-Pro Desktop % cd httprunner_demo macname@MacdeMacBook-Pro httprunner_demo % macname@MacdeMacBook-Pro httprunner_demo % ls -l total 8 -rw-r--r--@ 1 macname staff 185 12 10 09:47 debugtalk.py drwxr-xr-x 2 macname staff 64 12 10 09:47 har drwxr-xr-x 2 macname staff 64 12 10 09:47 reports drwxr-xr-x 4 macname staff 128 12 10 09:47 testcases macname@MacdeMacBook-Pro httprunner_demo %
#結構說明
debugtalk.py 放置在項目根目錄下(借鑒了pytest的conftest文件的設計)
.env 放置在項目根目錄下,可以用於存放一些環境變量
reports 文件夾:存儲 HTML 測試報告
testcases 用於存放測試用例
har 可以存放錄制導出的.har文件
具體用法會在后續中細講,本章不展開。我們可以點開生成的testcases文件夾下的測試用例,里面是提供了一個可運行的demo內容的,那先來運行一下看看。
#運行項目
macname@MacdeMacBook-Pro Desktop % macname@MacdeMacBook-Pro Desktop % hrun httprunner_demo 2020-12-10 09:53:27.334 | INFO | httprunner.make:__make:512 - make path: /Users/macname/Desktop/httprunner_demo 2020-12-10 09:53:27.340 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.343 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/macname/Desktop/httprunner_demo/.env 2020-12-10 09:53:27.344 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME 2020-12-10 09:53:27.344 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD 2020-12-10 09:53:27.350 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref.yml 2020-12-10 09:53:27.361 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.362 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request.yml 2020-12-10 09:53:27.363 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request_test.py 2020-12-10 09:53:27.364 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref_test.py 2020-12-10 09:53:27.375 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 09:53:27.375 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request.yml 2020-12-10 09:53:27.376 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ... reformatted /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_ref_test.py reformatted /Users/macname/Desktop/httprunner_demo/testcases/demo_testcase_request_test.py All done! ✨ 🍰 ✨ 2 files reformatted. 2020-12-10 09:53:27.704 | INFO | httprunner.cli:main_run:56 - start to run tests with pytest. HttpRunner version: 3.1.4 ====================================================== test session starts ======================================================= platform darwin -- Python 3.7.4, pytest-5.4.3, py-1.8.0, pluggy-0.13.0 rootdir: /Users/macname/Desktop plugins: metadata-1.11.0, html-2.1.1, ordering-0.6, cov-2.8.1 collected 2 items httprunner_demo/testcases/demo_testcase_request_test.py . [ 50%] httprunner_demo/testcases/demo_testcase_ref_test.py . [100%] ======================================================= 2 passed in 5.17s ======================================================== macname@MacdeMacBook-Pro Desktop %
#轉換為pytest文件
macname@MacdeMacBook-Pro Desktop % cd httprunner_demo/har macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % har2case yinxiang.har 2020-12-10 10:00:18.639 | INFO | httprunner.ext.har2case.core:gen_testcase:356 - Start to generate testcase from /Users/macname/Desktop/httprunner_demo/har/yinxiang.har 2020-12-10 10:00:18.639 | INFO | httprunner.ext.har2case.core:_make_testcase:347 - Extract info from HAR file and prepare for testcase. 2020-12-10 10:00:18.641 | INFO | httprunner.compat:ensure_testcase_v3:219 - ensure compatibility with testcase format v2 2020-12-10 10:00:18.643 | INFO | httprunner.loader:load_dot_env_file:127 - Loading environment variables from /Users/macname/Desktop/httprunner_demo/.env 2020-12-10 10:00:18.643 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: USERNAME 2020-12-10 10:00:18.644 | DEBUG | httprunner.utils:set_os_environ:33 - Set OS environment variable: PASSWORD 2020-12-10 10:00:18.644 | INFO | httprunner.make:make_testcase:349 - start to make testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang.har 2020-12-10 10:00:18.645 | INFO | httprunner.make:make_testcase:442 - generated testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py 2020-12-10 10:00:18.646 | INFO | httprunner.make:format_pytest_with_black:170 - format pytest cases with black ... reformatted /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py All done! ✨ 🍰 ✨ 1 file reformatted. 2020-12-10 10:00:19.095 | INFO | httprunner.ext.har2case.core:gen_testcase:377 - generated testcase: /Users/macname/Desktop/httprunner_demo/har/yinxiang_test.py Sentry is attempting to send 0 pending error messages Waiting up to 2 seconds Press Ctrl-C to quit macname@MacdeMacBook-Pro har % ls __init__.py yinxiang.har yinxiang_test.py macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har %
#轉為YAML:
har2case yinxiang.har -2y
#轉換為JSON:
har2case yinxiang.har -2j
結果:
macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % ls -l total 216 -rw-r--r-- 1 macname staff 48 12 10 10:00 __init__.py -rw-r--r-- 1 macname staff 42124 12 10 09:56 yinxiang.har -rw-r--r-- 1 macname staff 21468 12 10 10:05 yinxiang.json -rw-r--r-- 1 macname staff 14698 12 10 10:05 yinxiang.yml -rw-r--r-- 1 macname staff 17863 12 10 10:00 yinxiang_test.py macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har % macname@MacdeMacBook-Pro har %
httprunner可以支持三種格式的用例,分別是pytest、yaml和json。yaml和json是以前的版本所使用的用例格式,但是在3.x版本上,官方強烈建議使用的是pytest格式的用例。
上圖是來自官方的用例格式關系圖,可以看出來,httprunner再對於第三方導出的har文件進行了轉換處理,有的人喜歡轉換成json,有的人喜歡轉換成yaml。但是最終,還是通過解析json格式的文件,生成pytest的python文件。
既然最后都是要生成pytest,那何不一步到位呢?哈哈,我想這就是官方推薦pytest格式的原因吧。
我還是挺喜歡的,因為我對於pytest使用的較多,那么接下來也是基於pytest格式的用例進行解析。
#用例結構分析
錄制生成的case很便捷,但是這並不是說,不需要我們做任何的改動了。在實踐的過程中,我們仍然會根據我們實際項目的不同需求來對case作進一步的調整,所以徹底的了解case的構造尤為重要。
首先,我錄制了一個百度搜索“httprunner”的一個請求,轉換成pytest文件后如下:
可以看到:
- 每個testcase都是HttpRunner的子類
- 必須有兩個類屬性:config和teststeps。
- 單個teststeps列表中的單個Step內部通過鏈式調用(RunRequest().get().with_params().with_header().with_cookies().validate().assert_equal())
- config:配置測試用例級設置,包括基礎url、驗證、變量、導出。
- teststeps:teststep的列表(list[Step]),每個步驟對應於一個API請求,也可以調用另一個testcase。此外,還支持variables/extract/validate/hooks機制來創建極其復雜的測試場景。
- 鏈調用:可以看到一個case的請求,經過了各個環節的調用,這也是httprunner 3.x版本一大亮點。現在的ide編輯器越來越強大,比如你使用pycharm的話,都不用你怎么記憶用例的格式,順手就...(點)出來了,這或許也是官方推薦使用pytest的另一個原因吧,哈哈。
補一個官方完整的一個demo代碼,並說說httprunner中的用例與我自己編寫的測試用例之間的聯系。
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseRequestWithFunctions(HttpRunner): config = ( Config("request methods testcase with functions") .variables( **{ "foo1": "config_bar1", "foo2": "config_bar2", "expect_foo1": "config_bar1", "expect_foo2": "config_bar2", } ) .base_url("http://demo.qa.com") .verify(False) .export(*["foo3"]) ) teststeps = [ Step( RunRequest("get with params") .with_variables( **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.args.foo1", "bar11") .assert_equal("body.args.sum_v", "3") .assert_equal("body.args.foo2", "bar21") ), Step( RunRequest("post form data") .with_variables(**{"foo2": "bar23"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "$expect_foo1") .assert_equal("body.form.foo2", "bar23") .assert_equal("body.form.foo3", "bar21") ), ] if __name__ == "__main__": TestCaseRequestWithFunctions().test_start()
#
#
- httprunner中的testcase,其實說的就是上面的這一整個Python文件。
- teststeps列表中的Step,其實就是我自己編寫case時候的一個個def test_xxx():pass。
- 而每一個Step內部,依然是按照 傳參——調用接口——斷言,這樣的過程來的。
萬變不離其宗,httprunner框架目前看起來,確實可以讓編寫更加的便捷、簡潔,但是這只是目前從demo的過程中得到的結論,后面還需要落地實戰才可以。
我們了解到了config,在配置中,我們可以配置測試用例級級別的一些設置,比如基礎url、驗證、變量、導出。
我們一起來看,官方給出的一個例子:
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseRequestWithFunctions(HttpRunner): config = ( Config("request methods testcase with functions") .variables( **{ "foo1": "config_bar1", "foo2": "config_bar2", "expect_foo1": "config_bar1", "expect_foo2": "config_bar2", } ) .base_url("http://demo.qa.com") .verify(False) .export(*["foo3"]) ) teststeps = [ Step( RunRequest("get with params") .with_variables( **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} ) .get("/get") .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) .extract() .with_jmespath("body.args.foo2", "foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.args.foo1", "bar11") .assert_equal("body.args.sum_v", "3") .assert_equal("body.args.foo2", "bar21") ), Step( RunRequest("post form data") .with_variables(**{"foo2": "bar23"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "$expect_foo1") .assert_equal("body.form.foo2", "bar23") .assert_equal("body.form.foo3", "bar21") ), ] if __name__ == "__main__": TestCaseRequestWithFunctions().test_start()
一、name(必填)
即用例名稱,這是一個必填參數。測試用例名稱,將顯示在執行日志和測試報告中。比如,我在之前的百度搜索的case里,加入name。
二、base_url(選填)
其實這個配置一般在多環境切換中最常用。
比如你的這套測試用例在qa環境,uat環境都要使用,那么就可以把基礎地址(舉例
http://demo.qa.com),設置進去。在后面的teststep中,只需要填上接口的相對路徑就好了(舉例 /get)。
這樣的話,切換環境運行,只需要修改base_url即可。
三、variables(選填)
變量,這里可以存放一些公共的變量,可以在測試用例里引用。這里大家可以記住這個“公共”的詞眼,因為在后面的Step中,還會有步驟變量。
比如說,我的接口有個傳參是不變的,比如用戶名username,而且后面的沒個Step都會用到這個傳參,那么username就可以放在config的公共變量里。
另外,Step里的變量優先級是比config里的變量要高的,如果有2個同名的變量的話,那么引用的時候,是優先引用步驟里的變量的。
四、verify(選填)
用來決定是否驗證服務器TLS證書的開關。
通常設置為False,當請求https請求時,就會跳過驗證。如果你運行時候發現拋錯SSLError,可以檢查一下是不是verify沒傳,或者設置了True。
五、export(選填)
導出的變量,主要是用於Step之間參數的傳遞。還是以上面的官方代碼為例:
- 在config中配置export“foo3”這個變量。
- 在第一個Step中,.extract() 提取了"body.args.foo2"給變量“foo3”。
- 在第二個Step中,引用變量"foo3"。
像參數傳遞,提取這些點,會放在后面單獨講解,前面還是以熟悉框架為主。
測試用例分層模型
一個testcase里(就是一個pytest格式的Python文件)可以有一個或者多個測試步驟,就是teststeps[]列表里的Step。
我的理解每一個Step就可以類比成pytest框架下的def test_xxx()的用例函數,在Step里通常都會要請求API完成測試,也可以調用其他測試用例來完成更多的需求。
可以來看下官方的測試用例邏輯圖(2.x版本不同,3.x棄用了2.x的API概念):
可以看到,testsuite包含了testcase,testcase1需要依賴testcase2才可以完成,那么就可以在teststep12對其進行引用;而testcase2又依賴於testcase3,那么也可以在teststep22進行引用。
但是在整個testsuite下,這3個testcase都是相互獨立的,可以獨自運行。如果需要相互調用,則是在testcase內部去完成處理。
可能看起來有點繞,其實官方想表達的就是測試用例分層的一個思想:
- 測試用例(testcase)應該是完整且獨立的,每條測試用例應該是都可以獨立運行的
- 測試用例是測試步驟(teststep)的有序集合
- 測試用例集(testsuite)是測試用例的無序集合,集合中的測試用例應該都是相互獨立,不存在先后依賴關系的;如果確實存在先后依賴關系,那就需要在測試用例中完成依賴的處理
其實這一點,在我們自己使用pytest框架編寫測試用例的時候同樣貫徹到了。為了自動化測試的穩定性和可維護性,每個測試用例之間相互獨立是非常有必要的。
1.RunRequest(name)
RunRequest的參數名用於指定teststep名稱,它將顯示在執行日志和測試報告中。
2. .with_variables
用於存放變量,但是這里的是Step步驟里的變量,不同的Step的變量是相互獨立的。所以對於多個Step都要使用的變量,我們可以放到config的變量里去。
另外,如果config和Step里有重名的變量,那么當你引用這個變量的時候,Step變量會覆蓋config變量。
3. .method(url)
這里就是指定請求API的方法了,常用的get、post等等。如圖所示,就是用的get方法,括號里的url就是要請求的地址了。
這里要注意的是,如果在config里設置了基礎url,那么步驟里的url就只能設置相對路徑了。
4. .with_params
這個就簡單了,測接口不都得要傳參么,對於params類型的傳參,就放這就行了,key-value鍵值對的形式。對於body形式的傳參,看后面。
5. .with_headers
同樣,有header要帶上的就放這里。
6. .with_cookies
需要帶cookie的,可以用.with_cookies方法。
7. .with_data
對於body類型的傳參,可以用.with_data。
8. .with_json
如果是json類型的body請求體,可以用.with_json。
9. .extract
這里就是要做提取操作了,使用.with_jmespath(jmes_path: Text, var_name: Text)。
這里是采用了JMESPath語言,JMESPath是JSON的查詢語言,可以便捷的提取json中你需要的元素。
第一個參數是你的目標元素的jmespath表達式,第二個元素則是用來存放這個元素的變量,供有需要的引用。
這里不展開,后面單講。
10. .validate
斷言,我們測試最終就是要驗證接口返回是否符合預期。
那在httprunner框架中,可以使用assert_XXX(jmes_path: Text, expected_value: Any)來進行提取和驗證。
第一個參數還是jmespath表達式,第二個參數則是預期值。
assert_XXX這種方式相信用過自動化測試框架的都不會陌生,所以也非常容易上手。目前httprunner還是封裝了非常豐富的斷言方法的,相信可以滿足絕大多數的需求了。
teststeps = [ Step( RunTestCase("request with functions") .with_variables( **{"foo1": "testcase_ref_bar1", "expect_foo1": "testcase_ref_bar1"} ) .call(RequestWithFunctions) .export(*["foo3"]) ), Step( RunRequest("post form data") .with_variables(**{"foo1": "bar1"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "application/x-www-form-urlencoded", } ) .with_data("foo1=$foo1&foo2=$foo3") .validate() .assert_equal("status_code", 200) .assert_equal("body.form.foo1", "bar1") .assert_equal("body.form.foo2", "bar21") ), ]
1. RunTestCase(name)
這個參數呢還是一個名稱,畢竟RunTestCase還是一個Step,這個名稱同樣會在日志和報告中顯示。
2. .with_variables
這個變量跟RunRequest里的用法一樣。
3. .call
這里就是指定你要引用的testcase類名稱了。
4. .export
可以指定要導出的變量,以供后續Step引用。
可以看的.export()內部是一個列表[],這里可以用來導出多個變量。
一、運行testcase的幾種場景
1. 運行單個case
通常單個case的話我會在編輯器里用main方法運行,不過也可以用命令行運行,看你喜歡。
main方法里在類的后面調用test_start()方法即可。
命令行的話,就是直接在hrun后面加上case的路徑,就可以運行了。
hrun httprunner_demo\testcases\get_user_name_test.py
from httprunner import HttpRunner, Config, Step, RunRequest class TestCaseRequestWithGetUserName(HttpRunner): config = ( Config("test /getUserName") .base_url("http://localhost:5000") .verify(False) .export(*["username"]) ) teststeps = [ Step( RunRequest("getUserName") .get("/getUserName") .extract() .with_jmespath("body.username", "username") .validate() .assert_equal("body.username", "wesson") ), ] if __name__ == "__main__": TestCaseRequestWithGetUserName().test_start()#這里
2. 運行多個case
hrun httprunner_demo\testcases\get_user_name_test.py httprunner_demo\testcases\join_str_test.py
3. 運行整個case文件夾
hrun httprunner_demo\testcases\
二、運行YAML/JSON文件格式的case
當你轉換har文件時候,同級文件夾下,就會生成對應的pytest的文件,文件名稱的末尾會有_test。
如果運行YAML/JSON文件,其實httprunner會先把它們轉換為pytest格式的,再去運行。
所以,用httprunner 3.x版本的話,寫case的話還是直接用pytest格式吧。
三、運行pytest格式的文件
對應pytest文件來說,用hrun或者pytest命令運行都是可以的。
因為hrun只是封裝了pytest,所以pytest運行的所有參數,同樣可以在hrun后面加。
hrun -h
pytest -h
用例引用
#get_user_name_test.py
from httprunner import HttpRunner, Config, Step, RunRequest class TestCaseRequestWithGetUserName(HttpRunner): config = ( Config("test /getUserName") .base_url("http://localhost:5000") .verify(False) .export(*["username"])#這里定義出要導出的變量 ) teststeps = [ Step( RunRequest("getUserName") .get("/getUserName") .extract() .with_jmespath("body.username", "username")#提取出目標值,賦值給username變量 .validate() .assert_equal("body.username", "wesson") ), ] if __name__ == "__main__": TestCaseRequestWithGetUserName().test_start()
#TestCaseRequestWithJoinStr.py
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase from .get_user_name_test import TestCaseRequestWithGetUserName #記得要導入引用的類 class TestCaseRequestWithJoinStr(HttpRunner): config = ( Config("test /joinStr") .base_url("http://localhost:5000") .verify(False) ) teststeps = [ Step( RunTestCase("setUp getUserName") .call(TestCaseRequestWithGetUserName)#導入后就可以調用了 .export(*["username"])#在RunTestCase步驟中定義這個變量的導出 ), Step( RunRequest("joinStr") .get("/joinStr") .with_params(**{"str1": "hello", "str2": "$username"})#在第二個傳參中引用導出的變量 .validate() .assert_equal("body.result", "hello $username")#斷言的預期值也引用變量 ), ] if __name__ == "__main__": TestCaseRequestWithJoinStr().test_start()