一、官方首推pytest格式
httprunner可以支持三種格式的用例,分別是pytest、yaml和json。yaml和json是以前的版本所使用的用例格式,但是在3.x版本上,官方強烈建議使用的是pytest格式的用例。
上圖是來自官方的用例格式關系圖,可以看出來,httprunner再對於第三方導出的har文件進行了轉換處理,有的人喜歡轉換成json,有的人喜歡轉換成yaml。但是最終,還是通過解析json格式的文件,生成pytest的python文件。
二、用例結構
錄制生成的case很便捷,但是這並不是說,不需要我們做任何的改動了。在實踐的過程中,我們仍然會根據我們實際項目的不同需求來對case作進一步的調整,所以徹底的了解case的構造尤為重要。
首先,我錄制了一個百度搜索“httprunner”的一個請求,轉換成pytest文件后如下:
可以看到:
每個測試用例都是 HttpRunner 的子類(一個類即為一個測試用例),並且必須具有兩個類屬性:config
和 teststeps
。
- 每個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機制來創建極其復雜的測試場景。
- 鏈式調用:HttpRunner v3.x 的最強大功能之一是鏈式調用,使用它無需記住任何測試用例格式的詳細信息,並且在 IDE 中編寫測試用例時可以智能完成。可以看到一個case的請求,經過了各個環節的調用,這也是httprunner 3.x版本一大亮點。
三、httprunner的用例結構
補一個官方完整的一個demo代碼,並說說httprunner中的用例與自己編寫的測試用例之間的聯系。
1 # NOTE: Generated By HttpRunner v3.1.4 2 # FROM: testcases\demo_testcase_request.yml 3 4 5 from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase 6 7 8 class TestCaseDemoTestcaseRequest(HttpRunner): 9 10 config = ( 11 Config("request methods testcase with functions") 12 .variables( 13 **{ 14 "foo1": "config_bar1", 15 "foo2": "config_bar2", 16 "expect_foo1": "config_bar1", 17 "expect_foo2": "config_bar2", 18 } 19 ) 20 .base_url("https://postman-echo.com") 21 .verify(False) 22 .export(*["foo3"]) 23 ) 24 25 teststeps = [ 26 Step( 27 RunRequest("get with params") 28 .with_variables( 29 **{"foo1": "bar11", "foo2": "bar21", "sum_v": "${sum_two(1, 2)}"} 30 ) 31 .get("/get") 32 .with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"}) 33 .with_headers(**{"User-Agent": "HttpRunner/${get_httprunner_version()}"}) 34 .extract() 35 .with_jmespath("body.args.foo2", "foo3") 36 .validate() 37 .assert_equal("status_code", 200) 38 .assert_equal("body.args.foo1", "bar11") 39 .assert_equal("body.args.sum_v", "3") 40 .assert_equal("body.args.foo2", "bar21") 41 ), 42 Step( 43 RunRequest("post raw text") 44 .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) 45 .post("/post") 46 .with_headers( 47 **{ 48 "User-Agent": "HttpRunner/${get_httprunner_version()}", 49 "Content-Type": "text/plain", 50 } 51 ) 52 .with_data( 53 "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." 54 ) 55 .validate() 56 .assert_equal("status_code", 200) 57 .assert_equal( 58 "body.data", 59 "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", 60 ) 61 ), 62 Step( 63 RunRequest("post form data") 64 .with_variables(**{"foo2": "bar23"}) 65 .post("/post") 66 .with_headers( 67 **{ 68 "User-Agent": "HttpRunner/${get_httprunner_version()}", 69 "Content-Type": "application/x-www-form-urlencoded", 70 } 71 ) 72 .with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3") 73 .validate() 74 .assert_equal("status_code", 200) 75 .assert_equal("body.form.foo1", "$expect_foo1") 76 .assert_equal("body.form.foo2", "bar23") 77 .assert_equal("body.form.foo3", "bar21") 78 ), 79 ] 80 81 82 if __name__ == "__main__": 83 TestCaseDemoTestcaseRequest().test_start()
- httprunner中的testcase,其實說的就是上面的這一整個Python文件。
- teststeps列表中的Step,其實就是自己編寫case時候的一個個def test_xxx():pass。
- 而每一個Step內部,依然是按照 傳參——調用接口——斷言,這樣的過程來的。
四、測試用例-config
在配置中,我們可以配置測試用例級級別的一些設置,比如基礎url、驗證、變量、導出。
# NOTE: Generated By HttpRunner v3.1.4 # FROM: testcases\demo_testcase_request.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseDemoTestcaseRequest(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("https://postman-echo.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 raw text") .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "text/plain", } ) .with_data( "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." ) .validate() .assert_equal("status_code", 200) .assert_equal( "body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", ) ), 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__": TestCaseDemoTestcaseRequest().test_start()
上面是官方給出的一個例子
(1) name(必填)
即用例名稱,這是一個必填參數。測試用例名稱,將顯示在執行日志和測試報告中。比如,官方給出的case里,加入name。
執行用例:
pytest -s -v demo_testcase_ref_test.py
運行后,在debug日志里,可以看到用例名稱被展示出來。
(2) base_url(選填)
這個配置一般在多環境切換中最常用。
比如你的這套測試用例在多套環境(測試環境,生成環境)都要使用,那么就可以把基礎地址(舉例http://demo.qa.com),設置進去。在后面的teststep中,只需要填上接口的相對路徑就好了(舉例 /get)。
這樣的話,切換環境運行,只需要修改base_url即可。
(3) variables(選填) -- 變量和值一一對應
在 HttpRunner 中,支持變量聲明(variables)和引用($var)的機制。調用函數(${func($var)})
在 config 和 test 中均可以通過 variables 關鍵字定義變量,然后在測試步驟中可以通過 $ + 變量名稱 的方式引用變量。
區別在於:
在 config 中定義的變量為全局的,整個測試用例(testcase)的所有地方均可以引用;
在 test 中定義的變量作用域僅局限於當前測試步驟(teststep)
config這里可以存放一些公共的變量,可以在測試用例里引用。這里大家可以記住這個“公共”的詞眼,因為在后面的Step中,還會有步驟變量。
比如說,我的接口有個傳參是不變的,比如用戶名username,而且后面的每個Step都會用到這個傳參,那么username就可以放在config的公共變量里。
另外,Step里的變量優先級是比config里的變量要高的,如果step和config中variables聲明的參數名相同,在運行測試用例的時候會優先取step里面的參數值。
注意:一般寫用例的時候,最好把可能會變的參數單獨寫個變量。做到測試數據和代碼的分離,以便后續維護。
全局變量
如果要設置一個全局變量,需把變量聲明(variables)放到config下
pytest格式:
# NOTE: Generated By HttpRunner v3.1.4 # FROM: testcases\demo_testcase_request.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseDemoTestcaseRequest(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("https://postman-echo.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 raw text") .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "text/plain", } ) .with_data( "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." ) .validate() .assert_equal("status_code", 200) .assert_equal( "body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", ) ), 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__": TestCaseDemoTestcaseRequest().test_start()
ymal格式:
config: name: "request methods testcase with functions" variables: foo1: config_bar1 foo2: config_bar2 expect_foo1: config_bar1 expect_foo2: config_bar2 base_url: "https://postman-echo.com" verify: False export: ["foo3"] teststeps: - name: get with params variables: foo1: bar11 foo2: bar21 sum_v: "${sum_two(1, 2)}" request: method: GET url: /get params: foo1: $foo1 foo2: $foo2 sum_v: $sum_v headers: User-Agent: HttpRunner/${get_httprunner_version()} extract: foo3: "body.args.foo2" validate: - eq: ["status_code", 200] - eq: ["body.args.foo1", "bar11"] - eq: ["body.args.sum_v", "3"] - eq: ["body.args.foo2", "bar21"] - name: post raw text variables: foo1: "bar12" foo3: "bar32" request: method: POST url: /post headers: User-Agent: HttpRunner/${get_httprunner_version()} Content-Type: "text/plain" data: "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." validate: - eq: ["status_code", 200] - eq: ["body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32."] - name: post form data variables: foo2: bar23 request: method: POST url: /post headers: User-Agent: HttpRunner/${get_httprunner_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo2&foo3=$foo3" validate: - eq: ["status_code", 200] - eq: ["body.form.foo1", "$expect_foo1"] - eq: ["body.form.foo2", "bar23"] - eq: ["body.form.foo3", "bar21"]
引用foo1和foo2變量用$foo1
,$foo2
局部變量
如果在step下聲明的變量,作用域只在當前step下有效。聲明變量用with_variables,變量和對應值用鍵值對,如
Step( RunRequest("post raw text") .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "text/plain", } ) .with_data( "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." ) .validate() .assert_equal("status_code", 200) .assert_equal( "body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", ) ),
完整代碼如下:
# NOTE: Generated By HttpRunner v3.1.4 # FROM: testcases\demo_testcase_request.yml from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase class TestCaseDemoTestcaseRequest(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("https://postman-echo.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 raw text") .with_variables(**{"foo1": "bar12", "foo3": "bar32"}) .post("/post") .with_headers( **{ "User-Agent": "HttpRunner/${get_httprunner_version()}", "Content-Type": "text/plain", } ) .with_data( "This is expected to be sent back as part of response body: $foo1-$foo2-$foo3." ) .validate() .assert_equal("status_code", 200) .assert_equal( "body.data", "This is expected to be sent back as part of response body: bar12-$expect_foo2-bar32.", ) ), 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__": TestCaseDemoTestcaseRequest().test_start()
(4) verify(選填)
request請求一個https鏈接時,會驗證一次SSL證書。當目標網站使用的是自簽名證書時,則會拋出下圖的異常。
request模塊發送請求函數有個參數verify值默認為True
verify用來決定是否驗證服務器TLS證書的開關。
通常設置為False,當請求https請求時,就會跳過驗證。如果你運行時候發現拋錯SSLError,可以檢查一下是不是verify沒傳,或者設置了True。
解決方法:
在request下添加一個verify參數,值為false
config: name: "request methods testcase: reference testcase" variables: foo1: testsuite_config_bar1 expect_foo1: testsuite_config_bar1 expect_foo2: config_bar2 base_url: "https://postman-echo.com" verify: False #全局設置 request忽略對SSL證書的校驗 teststeps: - name: request with functions variables: foo1: testcase_ref_bar1 expect_foo1: testcase_ref_bar1 testcase: testcases/demo_testcase_request.yml export: - foo3 - name: post form data variables: foo1: bar1 request: verify:False # 局部設置 只對當前用例有效 request忽略對SSL證書的校驗 method: POST url: /post headers: User-Agent: HttpRunner/${get_httprunner_version()} Content-Type: "application/x-www-form-urlencoded" data: "foo1=$foo1&foo2=$foo3" validate: - eq: ["status_code", 200] - eq: ["body.form.foo1", "bar1"] - eq: ["body.form.foo2", "bar21"]
(5) export(選填)
導出的變量,主要是用於Step之間參數的傳遞。還是以上面的官方代碼為例:
可以指定要導出的變量,以供后續Step引用。
可以看的.export()內部是一個列表[],這里可以用來導出多個變量。
- 在config中配置export“foo3”這個變量。
- 在第一個Step中,.extract() 提取了"body.args.foo2"給變量“foo3”。
- 在第二個Step中,引用變量"foo3"。