一、官方首推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"。