前言:
在一个pytest
格式的Python文件
的testcase
里可以有一个或者多个测试步骤,就是teststeps[]
列表里的Step()
。
每一个Step()
就可以类比成pytest
框架下的def test_xxx()
的用例函数,在每一个Step
里描述请求API的基本信息,用到是是RunRequest()
类,当然也可以调用其他测试用例RunTestCase()
,这个后面再详解。
官方的测试用例逻辑图如下(2.x版本不同,3.x弃用了2.x的API概念):
官方想表达的就是测试用例分层的一个思想:
- 测试用例
(testcase)
应该是完整且独立的,每条测试用例应该是都可以独立运行的 - 测试用例是测试步骤
(teststep)
的有序集合 - 测试用例集
(testsuite)
是测试用例的无序集合,集合中的测试用例应该都是相互独立,不存在先后依赖关系的;如果确实存在先后依赖关系,那就需要在测试用例中完成依赖的处理
为了自动化测试的稳定性和可维护性,每个测试用例之间相互独立是非常有必要的。
teststeps-RunRequest
teststeps = [
Step(
RunRequest("发放收单成功").
setup_hook('${setup_request($request,$key)}')
.with_variables(
**{
"attach": "附件",
"bank": "ICBC1",
"batchNo": "20200801",
"depositBank": "工商",
"extInfo": "API001-商户004-攻城狮",
"foreignNationality": "CN",
"memo": "API_AUTO1",
"outOrderNo": get_outOrderNo(),
"personalIncomeTax": "0",
"phone": "$phone",
"postId": "10468",
"postName": "tester",
"serviceFee": "0",
"serviceType": "10002",
"shouldAmount": "0",
"totalFee": "0",
"nonce": get_nonce(),
"timestamp": get_current_time()}
)
.post(unified_order.UnifiedOrderApi.path)
.with_json(unified_order.UnifiedOrderApi.json)
.teardown_hook('${polling_assert(state_,30)}')
.extract()
.with_jmespath("body.data.reqNo", "reqNo")
.with_jmespath("body.data.outOrderNo", "outOrderNo")
.validate()
.assert_equal("status_code", 200)
.assert_equal("body.code", "0000")
.assert_equal("body.msg", "处理成功")
.assert_equal('${db_filed_states_with_parm(state_)}', 30)
.assert_equal('${db_filed_states_with_parm(balance_state_)}', 39)
.assert_equal('${db_filed_states_with_parm(channel_balance_state_)}', 39)
) ]
name:RunRequest("step name")
RunRequest
的参数名用于指定Step
的名称,它将显示在执行日志和测试报告中
with_variables(**{key:value...})
用于存放局部(步骤)变量,作用域是此Step
,不同的Step的变量是相互独立的。所以对于多个Step
都要使用的变量,我们可以放到config
的变量里去。
另外,如果config
和Step
里有重名的变量,那么当你引用这个变量的时候,Step
变量会覆盖config
变量。
method(url)
指定请求API
的方法了,常用的get
、post
等等。如图所示,就是用的post方法,括号里的url参数就是要请求的地址了。
这里值得注意的是,如果在config
里设置了base_url
,那么步骤里的url
就只能设置path
部分了。
以下是支持的method
类型:
def get(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.GET, url=url)
return RequestWithOptionalArgs(self.__step_context)
def post(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.POST, url=url)
return RequestWithOptionalArgs(self.__step_context)
def put(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.PUT, url=url)
return RequestWithOptionalArgs(self.__step_context)
def head(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.HEAD, url=url)
return RequestWithOptionalArgs(self.__step_context)
def delete(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.DELETE, url=url)
return RequestWithOptionalArgs(self.__step_context)
def options(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.OPTIONS, url=url)
return RequestWithOptionalArgs(self.__step_context)
def patch(self, url: Text) -> RequestWithOptionalArgs:
self.__step_context.request = TRequest(method=MethodEnum.PATCH, url=url)
return RequestWithOptionalArgs(self.__step_context)
API参数的方法
with_json(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
with_params(**{"foo1": "$foo1", "foo2": "$foo2", "sum_v": "$sum_v"})
with_data("foo1=$foo1&foo2=$foo2&foo3=$foo3")
with_headers(**{key:value})
with_cookies(**{key:value})
extract()
提取操作,后面紧跟着配合.with_jmespath(jmes_path: Text, var_name: Text)
使用。这里是采用了JMESPath语言,JMESPath是JSON的查询语言,可以便捷的提取json中你需要的元素。
- 第一个参数是你的目标元素的
jmespath
表达式, - 第二个元素则是用来存放这个元素的变量,即把从
json
提取出来的值赋值给一个变量
validate()
断言,测试最终就是要验证接口返回是否符合预期。配合断言方法使用assert_xxx(jmes_path: Text, expected_value: Any)
在httprunner
框架中,可以使用assert_xxx(jmes_path: Text, expected_value: Any)来进行提取和验证
。
- 第一个参数还是
jmespath
表达式 -
第二个参数是预期值
断言支持的类型
class StepRequestValidation(object):
def __init__(self, step_context: TStep):
self.__step_context = step_context
def assert_equal(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"equal": [jmes_path, expected_value, message]}
)
return self
def assert_not_equal(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"not_equal": [jmes_path, expected_value, message]}
)
return self
def assert_greater_than(
self, jmes_path: Text, expected_value: Union[int, float], message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"greater_than": [jmes_path, expected_value, message]}
)
return self
def assert_less_than(
self, jmes_path: Text, expected_value: Union[int, float], message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"less_than": [jmes_path, expected_value, message]}
)
return self
def assert_greater_or_equals(
self, jmes_path: Text, expected_value: Union[int, float], message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"greater_or_equals": [jmes_path, expected_value, message]}
)
return self
def assert_less_or_equals(
self, jmes_path: Text, expected_value: Union[int, float], message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"less_or_equals": [jmes_path, expected_value, message]}
)
return self
def assert_length_equal(
self, jmes_path: Text, expected_value: int, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"length_equal": [jmes_path, expected_value, message]}
)
return self
def assert_length_greater_than(
self, jmes_path: Text, expected_value: int, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"length_greater_than": [jmes_path, expected_value, message]}
)
return self
def assert_length_less_than(
self, jmes_path: Text, expected_value: int, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"length_less_than": [jmes_path, expected_value, message]}
)
return self
def assert_length_greater_or_equals(
self, jmes_path: Text, expected_value: int, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"length_greater_or_equals": [jmes_path, expected_value, message]}
)
return self
def assert_length_less_or_equals(
self, jmes_path: Text, expected_value: int, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"length_less_or_equals": [jmes_path, expected_value, message]}
)
return self
def assert_string_equals(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"string_equals": [jmes_path, expected_value, message]}
)
return self
def assert_startswith(
self, jmes_path: Text, expected_value: Text, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"startswith": [jmes_path, expected_value, message]}
)
return self
def assert_endswith(
self, jmes_path: Text, expected_value: Text, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"endswith": [jmes_path, expected_value, message]}
)
return self
def assert_regex_match(
self, jmes_path: Text, expected_value: Text, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"regex_match": [jmes_path, expected_value, message]}
)
return self
def assert_contains(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"contains": [jmes_path, expected_value, message]}
)
return self
def assert_contained_by(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"contained_by": [jmes_path, expected_value, message]}
)
return self
def assert_type_match(
self, jmes_path: Text, expected_value: Any, message: Text = ""
) -> "StepRequestValidation":
self.__step_context.validators.append(
{"type_match": [jmes_path, expected_value, message]}
)
return self