1, refactor ResponseObject;
2, response can be modified in teardown_hooks.
This commit is contained in:
debugtalk
2018-05-31 00:36:10 +08:00
parent 2dd9413674
commit 0b3c6665c2
9 changed files with 142 additions and 162 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
__title__ = 'HttpRunner' __title__ = 'HttpRunner'
__description__ = 'One-stop solution for HTTP(S) testing.' __description__ = 'One-stop solution for HTTP(S) testing.'
__url__ = 'https://github.com/HttpRunner/HttpRunner' __url__ = 'https://github.com/HttpRunner/HttpRunner'
__version__ = '1.4.4' __version__ = '1.4.5'
__author__ = 'debugtalk' __author__ = 'debugtalk'
__author_email__ = 'mail@debugtalk.com' __author_email__ = 'mail@debugtalk.com'
__license__ = 'MIT' __license__ = 'MIT'
+1 -1
View File
@@ -202,7 +202,7 @@ class Context(object):
check_value = resp_obj.extract_field(check_item) check_value = resp_obj.extract_field(check_item)
except exception.ParseResponseError: except exception.ParseResponseError:
msg = "failed to extract check item from response!\n" msg = "failed to extract check item from response!\n"
msg += "response: {}".format(resp_obj.resp_text) msg += "response content: {}".format(resp_obj.content)
raise exception.ParseResponseError(msg) raise exception.ParseResponseError(msg)
validator["check_value"] = check_value validator["check_value"] = check_value
+38 -32
View File
@@ -1,5 +1,6 @@
# encoding: utf-8 # encoding: utf-8
import json
import re import re
from httprunner import exception, logger, testcase, utils from httprunner import exception, logger, testcase, utils
@@ -16,35 +17,34 @@ class ResponseObject(object):
@param (requests.Response instance) resp_obj @param (requests.Response instance) resp_obj
""" """
self.resp_obj = resp_obj self.resp_obj = resp_obj
self.resp_text = resp_obj.text
self.resp_body = self.parsed_body()
def parsed_body(self): def __getattr__(self, key):
try: try:
return self.resp_obj.json() if key == "json":
except ValueError: value = self.resp_obj.json()
return self.resp_text else:
value = getattr(self.resp_obj, key)
def parsed_dict(self): self.__dict__[key] = value
return { return value
'status_code': self.resp_obj.status_code, except AttributeError:
'headers': self.resp_obj.headers, err_msg = "ResponseObject does not have attribute: {}".format(key)
'body': self.resp_body logger.log_error(err_msg)
} raise exception.ParamsError(err_msg)
def _extract_field_with_regex(self, field): def _extract_field_with_regex(self, field):
""" extract field from response content with regex. """ extract field from response content with regex.
requests.Response body could be json or html text. requests.Response body could be json or html text.
@param (str) field should only be regex string that matched r".*\(.*\).*" @param (str) field should only be regex string that matched r".*\(.*\).*"
e.g. e.g.
self.resp_text: "LB123abcRB789" self.text: "LB123abcRB789"
field: "LB[\d]*(.*)RB[\d]*" field: "LB[\d]*(.*)RB[\d]*"
return: abc return: abc
""" """
matched = re.search(field, self.resp_text) matched = re.search(field, self.text)
if not matched: if not matched:
err_msg = u"Failed to extract data with regex!\n" err_msg = u"Failed to extract data with regex!\n"
err_msg += u"response body: {}\n".format(self.resp_text) err_msg += u"response content: {}\n".format(self.content)
err_msg += u"regex: {}\n".format(field) err_msg += u"regex: {}\n".format(field)
logger.log_error(err_msg) logger.log_error(err_msg)
raise exception.ParamsError(err_msg) raise exception.ParamsError(err_msg)
@@ -56,6 +56,8 @@ class ResponseObject(object):
@param (str) field should be string joined by delimiter. @param (str) field should be string joined by delimiter.
e.g. e.g.
"status_code" "status_code"
"headers"
"cookies"
"content" "content"
"headers.content-type" "headers.content-type"
"content.person.name.first_name" "content.person.name.first_name"
@@ -69,10 +71,8 @@ class ResponseObject(object):
top_query = field top_query = field
sub_query = None sub_query = None
if top_query in ["body", "content", "text"]: if top_query == "cookies":
top_query_content = self.parsed_body() cookies = self.cookies
elif top_query == "cookies":
cookies = self.resp_obj.cookies
try: try:
return cookies[sub_query] return cookies[sub_query]
except KeyError: except KeyError:
@@ -81,21 +81,25 @@ class ResponseObject(object):
err_msg += u"attribute: {}".format(sub_query) err_msg += u"attribute: {}".format(sub_query)
logger.log_error(err_msg) logger.log_error(err_msg)
raise exception.ParamsError(err_msg) raise exception.ParamsError(err_msg)
else:
try: try:
top_query_content = getattr(self.resp_obj, top_query) top_query_content = getattr(self, top_query)
except AttributeError: except AttributeError:
err_msg = u"Failed to extract attribute from response object: resp_obj.{}".format(top_query) err_msg = u"Failed to extract attribute from response object: resp_obj.{}".format(top_query)
logger.log_error(err_msg) logger.log_error(err_msg)
raise exception.ParamsError(err_msg) raise exception.ParamsError(err_msg)
if sub_query: if sub_query:
if not isinstance(top_query_content, (dict, CaseInsensitiveDict, list)): if not isinstance(top_query_content, (dict, CaseInsensitiveDict, list)):
err_msg = u"Failed to extract data with delimiter!\n" try:
err_msg += u"response: {}\n".format(self.parsed_dict()) # TODO: remove compatibility for content, text
err_msg += u"regex: {}\n".format(field) top_query_content = json.loads(top_query_content)
logger.log_error(err_msg) except json.decoder.JSONDecodeError:
raise exception.ParamsError(err_msg) err_msg = u"Failed to extract data with delimiter!\n"
err_msg += u"response content: {}\n".format(self.content)
err_msg += u"regex: {}\n".format(field)
logger.log_error(err_msg)
raise exception.ParamsError(err_msg)
# e.g. key: resp_headers_content_type, sub_query = "content-type" # e.g. key: resp_headers_content_type, sub_query = "content-type"
return utils.query_json(top_query_content, sub_query) return utils.query_json(top_query_content, sub_query)
@@ -105,7 +109,7 @@ class ResponseObject(object):
except AttributeError: except AttributeError:
err_msg = u"Failed to extract value from response!\n" err_msg = u"Failed to extract value from response!\n"
err_msg += u"response: {}\n".format(self.parsed_dict()) err_msg += u"response content: {}\n".format(self.content)
err_msg += u"extract field: {}\n".format(field) err_msg += u"extract field: {}\n".format(field)
logger.log_error(err_msg) logger.log_error(err_msg)
raise exception.ParamsError(err_msg) raise exception.ParamsError(err_msg)
@@ -123,6 +127,8 @@ class ResponseObject(object):
msg += "\t=> {}".format(value) msg += "\t=> {}".format(value)
logger.log_debug(msg) logger.log_debug(msg)
# TODO: unify ParseResponseError type
except (exception.ParseResponseError, TypeError): except (exception.ParseResponseError, TypeError):
logger.log_error("failed to extract field: {}".format(field)) logger.log_error("failed to extract field: {}".format(field))
raise raise
+5 -5
View File
@@ -166,16 +166,16 @@ class Runner(object):
name=group_name, name=group_name,
**parsed_request **parsed_request
) )
resp_obj = response.ResponseObject(resp)
# teardown hooks # teardown hooks
teardown_hooks = testcase_dict.get("teardown_hooks", []) teardown_hooks = testcase_dict.get("teardown_hooks", [])
if teardown_hooks: if teardown_hooks:
self.context.bind_testcase_variable("response", resp) self.context.bind_testcase_variable("response", resp_obj)
self.do_hook_actions(teardown_hooks) self.do_hook_actions(teardown_hooks)
# extract # extract
extractors = testcase_dict.get("extract", []) or testcase_dict.get("extractors", []) extractors = testcase_dict.get("extract", []) or testcase_dict.get("extractors", [])
resp_obj = response.ResponseObject(resp)
extracted_variables_mapping = resp_obj.extract_response(extractors) extracted_variables_mapping = resp_obj.extract_response(extractors)
self.context.bind_extracted_variables(extracted_variables_mapping) self.context.bind_extracted_variables(extracted_variables_mapping)
@@ -194,9 +194,9 @@ class Runner(object):
# log response # log response
err_resp_msg = "response: \n" err_resp_msg = "response: \n"
err_resp_msg += "status_code: {}\n".format(resp.status_code) err_resp_msg += "status_code: {}\n".format(resp_obj.status_code)
err_resp_msg += "headers: {}\n".format(resp.headers) err_resp_msg += "headers: {}\n".format(resp_obj.headers)
err_resp_msg += "body: {}\n".format(resp.text) err_resp_msg += "content: {}\n".format(resp_obj.content)
logger.log_error(err_resp_msg) logger.log_error(err_resp_msg)
raise raise
-13
View File
@@ -95,19 +95,6 @@ def get_token():
response.headers["Content-Type"] = "application/json" response.headers["Content-Type"] = "application/json"
return response return response
@app.route('/customize-response', methods=['POST'])
def get_customized_response():
expected_resp_json = request.get_json()
status_code = expected_resp_json.get('status_code', 200)
headers_dict = expected_resp_json.get('headers', {})
body = expected_resp_json.get('body', {})
response = make_response(json.dumps(body), status_code)
for header_key, header_value in headers_dict.items():
response.headers[header_key] = header_value
return response
@app.route('/api/users') @app.route('/api/users')
@validate_request @validate_request
def get_users(): def get_users():
+5
View File
@@ -94,3 +94,8 @@ def setup_hook_httpntlmauth(request):
auth_account = request.pop("httpntlmauth") auth_account = request.pop("httpntlmauth")
request["auth"] = HttpNtlmAuth( request["auth"] = HttpNtlmAuth(
auth_account["username"], auth_account["password"]) auth_account["username"], auth_account["password"])
def alter_response(response):
response.status_code = 500
response.headers["Content-Type"] = "html/text"
response.json["headers"]["Host"] = "127.0.0.1:8888"
+14
View File
@@ -20,3 +20,17 @@
validate: validate:
- eq: ["status_code", 200] - eq: ["status_code", 200]
- eq: [content.headers.Host, "127.0.0.1:3458"] - eq: [content.headers.Host, "127.0.0.1:3458"]
- test:
name: alter response
request:
url: /headers
method: GET
teardown_hooks:
- ${alter_response($response)}
validate:
- eq: ["status_code", 500]
- eq: ["headers.content-type", "html/text"]
- eq: [json.headers.Host, "127.0.0.1:8888"]
- eq: [content.headers.Host, "127.0.0.1:3458"]
- eq: [text.headers.Host, "127.0.0.1:3458"]
-29
View File
@@ -113,32 +113,3 @@ class TestApiServer(ApiServerUnittest):
resp = self.delete_user(1000) resp = self.delete_user(1000)
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['success'], True) self.assertEqual(resp.json()['success'], True)
def test_get_customized_response_status_code(self):
status_code = random.randint(200, 511)
url = "%s/customize-response" % self.host
expected_response = {
'status_code': status_code,
}
resp = self.api_client.post(
url,
headers=self.headers,
json=expected_response
)
self.assertEqual(status_code, resp.status_code)
def test_get_customized_response_headers(self):
expected_response = {
'headers': {
'abc': 123,
'def': 456
}
}
url = "%s/customize-response" % self.host
resp = self.api_client.post(
url,
headers=self.headers,
json=expected_response
)
self.assertIn('abc', resp.headers)
self.assertIn('123', resp.headers['abc'])
+78 -81
View File
@@ -1,5 +1,6 @@
import requests import requests
from httprunner import exception, response, utils from httprunner import exception, response, utils
from httprunner.compat import bytes
from tests.base import ApiServerUnittest from tests.base import ApiServerUnittest
@@ -13,55 +14,76 @@ class TestResponse(ApiServerUnittest):
url = "http://127.0.0.1:5000/api/users" url = "http://127.0.0.1:5000/api/users"
resp = requests.get(url) resp = requests.get(url)
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
parsed_dict = resp_obj.parsed_dict() self.assertTrue(hasattr(resp_obj, 'status_code'))
self.assertIn('status_code', parsed_dict) self.assertTrue(hasattr(resp_obj, 'headers'))
self.assertIn('headers', parsed_dict) self.assertTrue(hasattr(resp_obj, 'content'))
self.assertIn('body', parsed_dict) self.assertIn('Content-Type', resp_obj.headers)
self.assertIn('Content-Type', parsed_dict['headers']) self.assertIn('Content-Length', resp_obj.headers)
self.assertIn('Content-Length', parsed_dict['headers']) self.assertIn('success', resp_obj.json)
self.assertIn('success', parsed_dict['body'])
def test_parse_response_object_text(self): def test_parse_response_object_content(self):
url = "http://127.0.0.1:5000/" url = "http://127.0.0.1:5000/"
resp = requests.get(url) resp = requests.get(url)
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
parsed_dict = resp_obj.parsed_dict() self.assertEqual(bytes, type(resp_obj.content))
self.assertIn('status_code', parsed_dict)
self.assertIn('headers', parsed_dict)
self.assertIn('body', parsed_dict)
self.assertIn('Content-Type', parsed_dict['headers'])
self.assertIn('Content-Length', parsed_dict['headers'])
self.assertTrue(str, type(parsed_dict['body']))
def test_extract_response_json(self): def test_extract_response_json(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ json={
'headers': { 'success': False,
'Content-Type': "application/json" "person": {
}, "name": {
'body': { "first_name": "Leo",
'success': False, "last_name": "Lee",
"person": { },
"name": { "age": 29,
"first_name": "Leo", "cities": ["Guangzhou", "Shenzhen"]
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
} }
} }
) )
# resp.text
# {
# "args": {},
# "data": "{\"success\": false, \"person\": {\"name\": {\"first_name\": \"Leo\", \"last_name\": \"Lee\"}, \"age\": 29, \"cities\": [\"Guangzhou\", \"Shenzhen\"]}}",
# "files": {},
# "form": {},
# "headers": {
# "Accept": "*/*",
# "Accept-Encoding": "gzip, deflate",
# "Connection": "keep-alive",
# "Content-Length": "129",
# "Content-Type": "application/json",
# "Host": "127.0.0.1:3458",
# "User-Agent": "python-requests/2.18.4"
# },
# "json": {
# "person": {
# "age": 29,
# "cities": [
# "Guangzhou",
# "Shenzhen"
# ],
# "name": {
# "first_name": "Leo",
# "last_name": "Lee"
# }
# },
# "success": false
# },
# "method": "POST",
# "origin": "127.0.0.1",
# "url": "http://127.0.0.1:3458/anything"
# }
extract_binds_list = [ extract_binds_list = [
{"resp_status_code": "status_code"}, {"resp_status_code": "status_code"},
{"resp_headers_content_type": "headers.content-type"}, {"resp_headers_content_type": "headers.content-type"},
{"resp_content_body_success": "body.success"}, {"resp_content_body_success": "json.json.success"},
{"resp_content_content_success": "content.success"}, {"resp_content_content_success": "content.json.success"},
{"resp_content_text_success": "text.success"}, {"resp_content_text_success": "text.json.success"},
{"resp_content_person_first_name": "content.person.name.first_name"}, {"resp_content_person_first_name": "content.json.person.name.first_name"},
{"resp_content_cities_1": "content.person.cities.1"} {"resp_content_cities_1": "content.json.person.cities.1"}
] ]
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list) extract_binds_dict = resp_obj.extract_response(extract_binds_list)
@@ -97,21 +119,16 @@ class TestResponse(ApiServerUnittest):
def test_extract_response_fail(self): def test_extract_response_fail(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ json={
'headers': { 'success': False,
'Content-Type': "application/json" "person": {
}, "name": {
'body': { "first_name": "Leo",
'success': False, "last_name": "Lee",
"person": { },
"name": { "age": 29,
"first_name": "Leo", "cities": ["Guangzhou", "Shenzhen"]
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
} }
} }
) )
@@ -134,17 +151,12 @@ class TestResponse(ApiServerUnittest):
def test_extract_response_json_string(self): def test_extract_response_json_string(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ data="abc"
'headers': {
'Content-Type': "application/json"
},
'body': "abc"
}
) )
extract_binds_list = [ extract_binds_list = [
{"resp_content_body": "content"} {"resp_content_body": "content.data"}
] ]
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
@@ -156,13 +168,8 @@ class TestResponse(ApiServerUnittest):
def test_extract_text_response(self): def test_extract_text_response(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ data="LB123abcRB789"
'headers': {
'Content-Type': "application/json"
},
'body': "LB123abcRB789"
}
) )
extract_binds_list = [ extract_binds_list = [
@@ -188,13 +195,8 @@ class TestResponse(ApiServerUnittest):
def test_extract_text_response_exception(self): def test_extract_text_response_exception(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ data="LB123abcRB789"
'headers': {
'Content-Type': "application/json"
},
'body': "LB123abcRB789"
}
) )
extract_binds_list = [ extract_binds_list = [
{"resp_content_key1": "LB123.*RB789"} {"resp_content_key1": "LB123.*RB789"}
@@ -205,28 +207,23 @@ class TestResponse(ApiServerUnittest):
def test_extract_response_empty(self): def test_extract_response_empty(self):
resp = requests.post( resp = requests.post(
url="http://127.0.0.1:5000/customize-response", url="http://127.0.0.1:3458/anything",
json={ data="abc"
'headers': {
'Content-Type': "application/json"
},
'body': ""
}
) )
extract_binds_list = [ extract_binds_list = [
{"resp_content_body": "content"} {"resp_content_body": "content.data"}
] ]
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list) extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual( self.assertEqual(
extract_binds_dict["resp_content_body"], extract_binds_dict["resp_content_body"],
"" 'abc'
) )
extract_binds_list = [ extract_binds_list = [
{"resp_content_body": "content.abc"} {"resp_content_body": "content.data.def"}
] ]
resp_obj = response.ResponseObject(resp) resp_obj = response.ResponseObject(resp)
with self.assertRaises(exception.ParamsError): with self.assertRaises(exception.ParseResponseError):
resp_obj.extract_response(extract_binds_list) resp_obj.extract_response(extract_binds_list)