refactor: format code with black

This commit is contained in:
debugtalk
2022-04-30 15:06:31 +08:00
parent 737379b49f
commit be8c053ed8
52 changed files with 1809 additions and 2254 deletions

View File

@@ -4,6 +4,7 @@
- fix #1217: reload debugtalk.py if loaded
- fix #1246: catch exceptions caused by GA report failure
- refactor: format code with black
## 2.5.8 (2022-03-23)

View File

@@ -37,42 +37,37 @@ token_dict = {}
def gen_random_string(str_len):
""" generate random string with specified length
"""
return ''.join(
random.choice(string.ascii_letters + string.digits) for _ in range(str_len))
"""generate random string with specified length"""
return "".join(
random.choice(string.ascii_letters + string.digits) for _ in range(str_len)
)
def get_sign(*args):
content = ''.join(args).encode('ascii')
sign_key = SECRET_KEY.encode('ascii')
content = "".join(args).encode("ascii")
sign_key = SECRET_KEY.encode("ascii")
sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
return sign
def gen_md5(*args):
return hashlib.md5("".join(args).encode('utf-8')).hexdigest()
return hashlib.md5("".join(args).encode("utf-8")).hexdigest()
def validate_request(func):
@wraps(func)
def wrapper(*args, **kwargs):
device_sn = request.headers.get('device_sn', "")
token = request.headers.get('token', "")
device_sn = request.headers.get("device_sn", "")
token = request.headers.get("token", "")
if not device_sn or not token:
result = {
'success': False,
'msg': "device_sn or token is null."
}
result = {"success": False, "msg": "device_sn or token is null."}
response = make_response(json.dumps(result), 401)
response.headers["Content-Type"] = "application/json"
return response
if token_dict.get(device_sn) != token:
result = {
'success': False,
'msg': "Authorization failed!"
}
result = {"success": False, "msg": "Authorization failed!"}
response = make_response(json.dumps(result), 403)
response.headers["Content-Type"] = "application/json"
return response
@@ -82,107 +77,89 @@ def validate_request(func):
return wrapper
@app.route('/')
@app.route("/")
def index():
return "Hello World!"
@app.route('/api/get-token', methods=['POST'])
@app.route("/api/get-token", methods=["POST"])
def get_token():
device_sn = request.headers.get('device_sn', "")
os_platform = request.headers.get('os_platform', "")
app_version = request.headers.get('app_version', "")
device_sn = request.headers.get("device_sn", "")
os_platform = request.headers.get("os_platform", "")
app_version = request.headers.get("app_version", "")
data = request.get_json()
sign = data.get('sign', "")
sign = data.get("sign", "")
expected_sign = get_sign(device_sn, os_platform, app_version)
if expected_sign != sign:
result = {
'success': False,
'msg': "Authorization failed!"
}
result = {"success": False, "msg": "Authorization failed!"}
response = make_response(json.dumps(result), 403)
else:
token = gen_random_string(16)
token_dict[device_sn] = token
result = {
'success': True,
'token': token
}
result = {"success": True, "token": token}
response = make_response(json.dumps(result))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users')
@app.route("/api/users")
@validate_request
def get_users():
users_list = [user for uid, user in users_dict.items()]
users = {
'success': True,
'count': len(users_list),
'items': users_list
}
users = {"success": True, "count": len(users_list), "items": users_list}
response = make_response(json.dumps(users))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/reset-all')
@app.route("/api/reset-all")
@validate_request
def clear_users():
users_dict.clear()
result = {
'success': True
}
result = {"success": True}
response = make_response(json.dumps(result))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>', methods=['POST'])
@app.route("/api/users/<int:uid>", methods=["POST"])
@validate_request
def create_user(uid):
user = request.get_json()
if uid not in users_dict:
result = {
'success': True,
'msg': "user created successfully."
}
result = {"success": True, "msg": "user created successfully."}
status_code = 201
users_dict[uid] = user
else:
result = {
'success': False,
'msg': "user already existed."
}
result = {"success": False, "msg": "user already existed."}
status_code = 500
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>')
@app.route("/api/users/<int:uid>")
@validate_request
def get_user(uid):
user = users_dict.get(uid, {})
if user:
result = {
'success': True,
'data': user
}
result = {"success": True, "data": user}
status_code = 200
else:
result = {
'success': False,
'data': user
}
result = {"success": False, "data": user}
status_code = 404
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>', methods=['PUT'])
@app.route("/api/users/<int:uid>", methods=["PUT"])
@validate_request
def update_user(uid):
user = users_dict.get(uid, {})
@@ -195,15 +172,13 @@ def update_user(uid):
success = False
status_code = 404
result = {
'success': success,
'data': user
}
result = {"success": success, "data": user}
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>', methods=['DELETE'])
@app.route("/api/users/<int:uid>", methods=["DELETE"])
@validate_request
def delete_user(uid):
user = users_dict.pop(uid, {})
@@ -214,10 +189,7 @@ def delete_user(uid):
success = False
status_code = 404
result = {
'success': success,
'data': user
}
result = {"success": success, "data": user}
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response

View File

@@ -6,43 +6,41 @@ import time
SECRET_KEY = "DebugTalk"
def gen_random_string(str_len):
random_char_list = []
for _ in range(str_len):
random_char = random.choice(string.ascii_letters + string.digits)
random_char_list.append(random_char)
random_string = ''.join(random_char_list)
random_string = "".join(random_char_list)
return random_string
def get_sign(*args):
content = ''.join(args).encode('ascii')
sign_key = SECRET_KEY.encode('ascii')
content = "".join(args).encode("ascii")
sign_key = SECRET_KEY.encode("ascii")
sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
return sign
def gen_user_id():
return int(time.time() * 1000)
def get_user_id():
return [
{"user_id": 1001},
{"user_id": 1002},
{"user_id": 1003},
{"user_id": 1004}
]
return [{"user_id": 1001}, {"user_id": 1002}, {"user_id": 1003}, {"user_id": 1004}]
def get_account(num):
accounts = []
for index in range(1, num+1):
for index in range(1, num + 1):
accounts.append(
{"username": "user%s" % index, "password": str(index) * 6},
)
return accounts
def get_os_platform():
return [
{"os_platform": "ios"},
{"os_platform": "android"}
]
return [{"os_platform": "ios"}, {"os_platform": "android"}]

View File

@@ -7,5 +7,5 @@ __all__ = [
klook_len_eq,
check_search_area_result,
exists_default_group,
teardown_hook_set_encoding
teardown_hook_set_encoding,
]

View File

@@ -2,7 +2,7 @@ def check_search_area_result(content, expect_name):
print(content, expect_name)
found = False
for item in content:
if item['fullName'] == expect_name:
if item["fullName"] == expect_name:
found = True
break
assert found
@@ -11,8 +11,12 @@ def check_search_area_result(content, expect_name):
def exists_default_group(content, expect):
found = False
for item in content:
if item['defaultGroup']:
print('defaultGroup found, id={}, parentAreaId={}'.format(item['id'], item['parentAreaId']))
if item["defaultGroup"]:
print(
"defaultGroup found, id={}, parentAreaId={}".format(
item["id"], item["parentAreaId"]
)
)
found = True
break
assert found

View File

@@ -1,28 +1,38 @@
import os
import unittest
from httprunner import (__version__, exceptions, loader, logger, parser,
report, runner, utils)
from httprunner import (
__version__,
exceptions,
loader,
logger,
parser,
report,
runner,
utils,
)
from httprunner.utils import ga_client
class HttpRunner(object):
""" Developer Interface: Main Interface
Usage:
"""Developer Interface: Main Interface
Usage:
from httprunner.api import HttpRunner
runner = HttpRunner(
failfast=True,
save_tests=True,
log_level="INFO",
log_file="test.log"
)
summary = runner.run(path_or_tests)
from httprunner.api import HttpRunner
runner = HttpRunner(
failfast=True,
save_tests=True,
log_level="INFO",
log_file="test.log"
)
summary = runner.run(path_or_tests)
"""
def __init__(self, failfast=False, save_tests=False, log_level="WARNING", log_file=None):
""" initialize HttpRunner.
def __init__(
self, failfast=False, save_tests=False, log_level="WARNING", log_file=None
):
"""initialize HttpRunner.
Args:
failfast (bool): stop the test run on the first error or failure.
@@ -34,10 +44,7 @@ class HttpRunner(object):
logger.setup_logger(log_level, log_file)
self.exception_stage = "initialize HttpRunner()"
kwargs = {
"failfast": failfast,
"resultclass": report.HtmlTestResult
}
kwargs = {"failfast": failfast, "resultclass": report.HtmlTestResult}
self.unittest_runner = unittest.TextTestRunner(**kwargs)
self.test_loader = unittest.TestLoader()
self.save_tests = save_tests
@@ -45,7 +52,7 @@ class HttpRunner(object):
self.project_working_directory = None
def _add_tests(self, testcases):
""" initialize testcase with Runner() and add to test suite.
"""initialize testcase with Runner() and add to test suite.
Args:
testcases (list): testcases list.
@@ -54,9 +61,10 @@ class HttpRunner(object):
unittest.TestSuite()
"""
def _add_test(test_runner, test_dict):
""" add test to testcase.
"""
"""add test to testcase."""
def test(self):
try:
test_runner.run_test(test_dict)
@@ -89,7 +97,7 @@ class HttpRunner(object):
for testcase in testcases:
config = testcase.get("config", {})
test_runner = runner.Runner(config)
TestSequense = type('TestSequense', (unittest.TestCase,), {})
TestSequense = type("TestSequense", (unittest.TestCase,), {})
tests = testcase.get("teststeps", [])
for index, test_dict in enumerate(tests):
@@ -98,12 +106,13 @@ class HttpRunner(object):
times = int(times)
except ValueError:
raise exceptions.ParamsError(
"times should be digit, given: {}".format(times))
"times should be digit, given: {}".format(times)
)
for times_index in range(times):
# suppose one testcase should not have more than 9999 steps,
# and one step should not run more than 999 times.
test_method_name = 'test_{:04}_{:03}'.format(index, times_index)
test_method_name = "test_{:04}_{:03}".format(index, times_index)
test_method = _add_test(test_runner, test_dict)
setattr(TestSequense, test_method_name, test_method)
@@ -116,7 +125,7 @@ class HttpRunner(object):
return test_suite
def _run_suite(self, test_suite):
""" run tests in test_suite
"""run tests in test_suite
Args:
test_suite: unittest.TestSuite()
@@ -140,7 +149,7 @@ class HttpRunner(object):
return tests_results
def _aggregate(self, tests_results):
""" aggregate results
"""aggregate results
Args:
tests_results (list): list of (testcase, result)
@@ -149,16 +158,12 @@ class HttpRunner(object):
summary = {
"success": True,
"stat": {
"testcases": {
"total": len(tests_results),
"success": 0,
"fail": 0
},
"teststeps": {}
"testcases": {"total": len(tests_results), "success": 0, "fail": 0},
"teststeps": {},
},
"time": {},
"platform": report.get_platform(),
"details": []
"details": [],
}
for tests_result in tests_results:
@@ -174,7 +179,9 @@ class HttpRunner(object):
testcase_summary["name"] = testcase.config.get("name")
testcase_summary["in_out"] = utils.get_testcase_io(testcase)
report.aggregate_stat(summary["stat"]["teststeps"], testcase_summary["stat"])
report.aggregate_stat(
summary["stat"]["teststeps"], testcase_summary["stat"]
)
report.aggregate_stat(summary["time"], testcase_summary["time"])
summary["details"].append(testcase_summary)
@@ -182,8 +189,7 @@ class HttpRunner(object):
return summary
def run_tests(self, tests_mapping):
""" run testcase/testsuite data
"""
"""run testcase/testsuite data"""
ga_client.track_event("RunAPITests", "hrun")
project_mapping = tests_mapping.get("project_mapping", {})
self.project_working_directory = project_mapping.get("PWD", os.getcwd())
@@ -231,7 +237,7 @@ class HttpRunner(object):
return self._summary
def get_vars_out(self):
""" get variables and output
"""get variables and output
Returns:
list: list of variables and output.
if tests are parameterized, list items are corresponded to parameters.
@@ -254,13 +260,10 @@ class HttpRunner(object):
if not self._summary:
return None
return [
summary["in_out"]
for summary in self._summary["details"]
]
return [summary["in_out"] for summary in self._summary["details"]]
def run_path(self, path, dot_env_path=None, mapping=None):
""" run testcase/testsuite file or folder.
"""run testcase/testsuite file or folder.
Args:
path (str): testcase/testsuite file/foler path.
@@ -281,7 +284,7 @@ class HttpRunner(object):
return self.run_tests(tests_mapping)
def run(self, path_or_tests, dot_env_path=None, mapping=None):
""" main interface.
"""main interface.
Args:
path_or_tests:
@@ -298,8 +301,12 @@ class HttpRunner(object):
if loader.is_test_path(path_or_tests):
return self.run_path(path_or_tests, dot_env_path, mapping)
elif loader.is_test_content(path_or_tests):
project_working_directory = path_or_tests.get("project_mapping", {}).get("PWD", os.getcwd())
project_working_directory = path_or_tests.get("project_mapping", {}).get(
"PWD", os.getcwd()
)
loader.init_pwd(project_working_directory)
return self.run_tests(path_or_tests)
else:
raise exceptions.ParamsError("Invalid testcase path or testcases: {}".format(path_or_tests))
raise exceptions.ParamsError(
"Invalid testcase path or testcases: {}".format(path_or_tests)
)

View File

@@ -108,4 +108,4 @@ def _cast_to_int(expect_value):
try:
return int(expect_value)
except Exception:
raise AssertionError("%r can't cast to int" % str(expect_value))
raise AssertionError("%r can't cast to int" % str(expect_value))

View File

@@ -12,15 +12,14 @@ from httprunner.exceptions import ParamsError
def gen_random_string(str_len):
""" generate random string with specified length
"""
return ''.join(
random.choice(string.ascii_letters + string.digits) for _ in range(str_len))
"""generate random string with specified length"""
return "".join(
random.choice(string.ascii_letters + string.digits) for _ in range(str_len)
)
def get_timestamp(str_len=13):
""" get timestamp string, length can only between 0 and 16
"""
"""get timestamp string, length can only between 0 and 16"""
if isinstance(str_len, integer_types) and 0 < str_len < 17:
return builtin_str(time.time()).replace(".", "")[:str_len]
@@ -28,13 +27,10 @@ def get_timestamp(str_len=13):
def get_current_date(fmt="%Y-%m-%d"):
""" get current date, default format is %Y-%m-%d
"""
"""get current date, default format is %Y-%m-%d"""
return datetime.datetime.now().strftime(fmt)
def sleep(n_secs):
""" sleep n seconds
"""
"""sleep n seconds"""
time.sleep(n_secs)

View File

@@ -10,58 +10,63 @@ from httprunner.compat import is_py2
from httprunner.loader import load_cases
from httprunner.logger import color_print, log_error
from httprunner.report import gen_html_report
from httprunner.utils import (create_scaffold, get_python2_retire_msg,
prettify_json_file, init_sentry_sdk)
from httprunner.utils import (
create_scaffold,
get_python2_retire_msg,
prettify_json_file,
init_sentry_sdk,
)
init_sentry_sdk()
def main():
""" API test: parse command line options and run commands.
"""
"""API test: parse command line options and run commands."""
if is_py2:
color_print(get_python2_retire_msg(), "YELLOW")
parser = argparse.ArgumentParser(description=__description__)
parser.add_argument(
'-V', '--version', dest='version', action='store_true',
help="show version")
"-V", "--version", dest="version", action="store_true", help="show version"
)
parser.add_argument(
'testfile_paths', nargs='*',
help="Specify api/testcase/testsuite file paths to run.")
"testfile_paths",
nargs="*",
help="Specify api/testcase/testsuite file paths to run.",
)
parser.add_argument(
'--log-level', default='INFO',
help="Specify logging level, default is INFO.")
"--log-level", default="INFO", help="Specify logging level, default is INFO."
)
parser.add_argument("--log-file", help="Write logs to specified file path.")
parser.add_argument(
'--log-file',
help="Write logs to specified file path.")
"--dot-env-path",
help="Specify .env file path, which is useful for keeping sensitive data.",
)
parser.add_argument("--report-template", help="Specify report template path.")
parser.add_argument("--report-dir", help="Specify report save directory.")
parser.add_argument(
'--dot-env-path',
help="Specify .env file path, which is useful for keeping sensitive data.")
"--report-file",
help="Specify report file path, this has higher priority than specifying report dir.",
)
parser.add_argument(
'--report-template',
help="Specify report template path.")
"--save-tests",
action="store_true",
default=False,
help="Save loaded/parsed/vars_out/summary json data to JSON files.",
)
parser.add_argument(
'--report-dir',
help="Specify report save directory.")
"--failfast",
action="store_true",
default=False,
help="Stop the test run on the first error or failure.",
)
parser.add_argument("--startproject", help="Specify new project name.")
parser.add_argument(
'--report-file',
help="Specify report file path, this has higher priority than specifying report dir.")
parser.add_argument(
'--save-tests', action='store_true', default=False,
help="Save loaded/parsed/vars_out/summary json data to JSON files.")
parser.add_argument(
'--failfast', action='store_true', default=False,
help="Stop the test run on the first error or failure.")
parser.add_argument(
'--startproject',
help="Specify new project name.")
parser.add_argument(
'--validate', nargs='*',
help="Validate YAML/JSON api/testcase/testsuite format.")
parser.add_argument(
'--prettify', nargs='*',
help="Prettify JSON testcase format.")
"--validate",
nargs="*",
help="Validate YAML/JSON api/testcase/testsuite format.",
)
parser.add_argument("--prettify", nargs="*", help="Prettify JSON testcase format.")
args = parser.parse_args()
@@ -99,23 +104,28 @@ def main():
failfast=args.failfast,
save_tests=args.save_tests,
log_level=args.log_level,
log_file=args.log_file
log_file=args.log_file,
)
err_code = 0
try:
for path in args.testfile_paths:
summary = runner.run(path, dot_env_path=args.dot_env_path)
report_dir = args.report_dir or os.path.join(runner.project_working_directory, "reports")
report_dir = args.report_dir or os.path.join(
runner.project_working_directory, "reports"
)
gen_html_report(
summary,
report_template=args.report_template,
report_dir=report_dir,
report_file=args.report_file
report_file=args.report_file,
)
err_code |= (0 if summary and summary["success"] else 1)
err_code |= 0 if summary and summary["success"] else 1
except Exception as ex:
color_print("!!!!!!!!!! exception stage: {} !!!!!!!!!!".format(runner.exception_stage), "YELLOW")
color_print(
"!!!!!!!!!! exception stage: {} !!!!!!!!!!".format(runner.exception_stage),
"YELLOW",
)
color_print(str(ex), "RED")
sentry_sdk.capture_exception(ex)
err_code = 1
@@ -123,5 +133,5 @@ def main():
sys.exit(err_code)
if __name__ == '__main__':
if __name__ == "__main__":
main()

View File

@@ -5,8 +5,12 @@ import time
import requests
import urllib3
from requests import Request, Response
from requests.exceptions import (InvalidSchema, InvalidURL, MissingSchema,
RequestException)
from requests.exceptions import (
InvalidSchema,
InvalidURL,
MissingSchema,
RequestException,
)
from httprunner import logger, response
from httprunner.utils import lower_dict_keys, omit_long_data
@@ -15,18 +19,15 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
def get_req_resp_record(resp_obj):
""" get request and response info from Response() object.
"""
"""get request and response info from Response() object."""
def log_print(req_resp_dict, r_type):
msg = "\n================== {} details ==================\n".format(r_type)
for key, value in req_resp_dict[r_type].items():
msg += "{:<16} : {}\n".format(key, repr(value))
logger.log_debug(msg)
req_resp_dict = {
"request": {},
"response": {}
}
req_resp_dict = {"request": {}, "response": {}}
# record actual request info
req_resp_dict["request"]["url"] = resp_obj.request.url
@@ -35,9 +36,9 @@ def get_req_resp_record(resp_obj):
request_body = resp_obj.request.body
if request_body:
request_content_type = lower_dict_keys(
req_resp_dict["request"]["headers"]
).get("content-type")
request_content_type = lower_dict_keys(req_resp_dict["request"]["headers"]).get(
"content-type"
)
if request_content_type and "multipart/form-data" in request_content_type:
# upload file type
req_resp_dict["request"]["body"] = "upload file stream (OMITTED)"
@@ -83,9 +84,8 @@ def get_req_resp_record(resp_obj):
class ApiResponse(Response):
def raise_for_status(self):
if hasattr(self, 'error') and self.error:
if hasattr(self, "error") and self.error:
raise self.error
Response.raise_for_status(self)
@@ -99,35 +99,31 @@ class HttpSession(requests.Session):
This is a slightly extended version of `python-request <http://python-requests.org>`_'s
:py:class:`requests.Session` class and mostly this class works exactly the same.
"""
def __init__(self):
super(HttpSession, self).__init__()
self.init_meta_data()
def init_meta_data(self):
""" initialize meta_data, it will store detail data of request and response
"""
"""initialize meta_data, it will store detail data of request and response"""
self.meta_data = {
"name": "",
"data": [
{
"request": {
"url": "N/A",
"method": "N/A",
"headers": {}
},
"request": {"url": "N/A", "method": "N/A", "headers": {}},
"response": {
"status_code": "N/A",
"headers": {},
"encoding": None,
"content_type": ""
}
"content_type": "",
},
}
],
"stat": {
"content_size": "N/A",
"response_time_ms": "N/A",
"elapsed_ms": "N/A",
}
},
}
def update_last_req_resp_record(self, resp_obj):
@@ -202,26 +198,23 @@ class HttpSession(requests.Session):
self.meta_data["stat"] = {
"response_time_ms": response_time_ms,
"elapsed_ms": response.elapsed.microseconds / 1000.0,
"content_size": content_size
"content_size": content_size,
}
# record request and response histories, include 30X redirection
response_list = response.history + [response]
self.meta_data["data"] = [
get_req_resp_record(resp_obj)
for resp_obj in response_list
get_req_resp_record(resp_obj) for resp_obj in response_list
]
try:
response.raise_for_status()
except RequestException as e:
logger.log_error(u"{exception}".format(exception=str(e)))
logger.log_error("{exception}".format(exception=str(e)))
else:
logger.log_info(
"""status_code: {}, response_time(ms): {} ms, response_length: {} bytes\n""".format(
response.status_code,
response_time_ms,
content_size
response.status_code, response_time_ms, content_size
)
)

View File

@@ -23,10 +23,10 @@ import sys
_ver = sys.version_info
#: Python 2.x?
is_py2 = (_ver[0] == 2)
is_py2 = _ver[0] == 2
#: Python 3.x?
is_py3 = (_ver[0] == 3)
is_py3 = _ver[0] == 3
# ---------

View File

@@ -2,7 +2,7 @@ from httprunner import parser, utils
class SessionContext(object):
""" HttpRunner session, store runtime variables.
"""HttpRunner session, store runtime variables.
Examples:
>>> variables = {"SECRET_KEY": "DebugTalk"}
@@ -16,12 +16,14 @@ class SessionContext(object):
def __init__(self, variables=None):
variables_mapping = utils.ensure_mapping_format(variables or {})
self.session_variables_mapping = parser.parse_variables_mapping(variables_mapping)
self.session_variables_mapping = parser.parse_variables_mapping(
variables_mapping
)
self.test_variables_mapping = {}
self.init_test_variables()
def init_test_variables(self, variables_mapping=None):
""" init test variables, called when each test(api) starts.
"""init test variables, called when each test(api) starts.
variables_mapping will be evaluated first.
Args:
@@ -45,20 +47,19 @@ class SessionContext(object):
self.test_variables_mapping.update(self.session_variables_mapping)
def update_test_variables(self, variable_name, variable_value):
""" update test variables, these variables are only valid in the current test.
"""
"""update test variables, these variables are only valid in the current test."""
self.test_variables_mapping[variable_name] = variable_value
def update_session_variables(self, variables_mapping):
""" update session with extracted variables mapping.
these variables are valid in the whole running session.
"""update session with extracted variables mapping.
these variables are valid in the whole running session.
"""
variables_mapping = utils.ensure_mapping_format(variables_mapping)
self.session_variables_mapping.update(variables_mapping)
self.test_variables_mapping.update(self.session_variables_mapping)
def eval_content(self, content):
""" evaluate content recursively, take effect on each variable and function in content.
content may be in any data structure, include dict, list, tuple, number, string, etc.
"""evaluate content recursively, take effect on each variable and function in content.
content may be in any data structure, include dict, list, tuple, number, string, etc.
"""
return parser.parse_lazy_data(content, self.test_variables_mapping)

View File

@@ -81,5 +81,4 @@ class TestcaseNotFound(NotFoundError):
class SummaryEmpty(MyBaseError):
""" test result summary data is empty
"""
"""test result summary data is empty"""

View File

@@ -1,6 +1,7 @@
try:
# monkey patch ssl at beginning to avoid RecursionError when running locust.
from gevent import monkey
monkey.patch_ssl()
from locust import main as locust_main
except ImportError:
@@ -11,6 +12,7 @@ $ pip install locustio
"""
print(msg)
import sys
sys.exit(0)
import io
@@ -26,9 +28,9 @@ init_sentry_sdk()
def parse_locustfile(file_path):
""" parse testcase file and return locustfile path.
if file_path is a Python file, assume it is a locustfile
if file_path is a YAML/JSON file, convert it to locustfile
"""parse testcase file and return locustfile path.
if file_path is a Python file, assume it is a locustfile
if file_path is a YAML/JSON file, convert it to locustfile
"""
if not os.path.isfile(file_path):
logger.color_print("file path invalid, exit.", "RED")
@@ -37,7 +39,7 @@ def parse_locustfile(file_path):
file_suffix = os.path.splitext(file_path)[1]
if file_suffix == ".py":
locustfile_path = file_path
elif file_suffix in ['.yaml', '.yml', '.json']:
elif file_suffix in [".yaml", ".yml", ".json"]:
locustfile_path = gen_locustfile(file_path)
else:
# '' or other suffix
@@ -48,18 +50,18 @@ def parse_locustfile(file_path):
def gen_locustfile(testcase_file_path):
""" generate locustfile from template.
"""
locustfile_path = 'locustfile.py'
"""generate locustfile from template."""
locustfile_path = "locustfile.py"
template_path = os.path.join(
os.path.dirname(os.path.realpath(__file__)),
"locustfile_template.py"
os.path.dirname(os.path.realpath(__file__)), "locustfile_template.py"
)
with io.open(template_path, encoding='utf-8') as template:
with io.open(locustfile_path, 'w', encoding='utf-8') as locustfile:
with io.open(template_path, encoding="utf-8") as template:
with io.open(locustfile_path, "w", encoding="utf-8") as locustfile:
template_content = template.read()
template_content = template_content.replace("$TESTCASE_FILE", testcase_file_path)
template_content = template_content.replace(
"$TESTCASE_FILE", testcase_file_path
)
locustfile.write(template_content)
return locustfile_path
@@ -67,6 +69,7 @@ def gen_locustfile(testcase_file_path):
def start_locust_main():
from httprunner.utils import ga_client
ga_client.track_event("RunLoadTests", "locust")
locust_main.main()
@@ -106,10 +109,9 @@ def run_locusts_with_processes(sys_argv, processes_count):
def main():
""" Performance test with locust: parse command line options and run commands.
"""
"""Performance test with locust: parse command line options and run commands."""
print("HttpRunner version: {}".format(__version__))
sys.argv[0] = 'locust'
sys.argv[0] = "locust"
if len(sys.argv) == 1:
sys.argv.extend(["-h"])
@@ -147,29 +149,36 @@ def main():
sys.argv[testcase_index] = parse_locustfile(testcase_file_path)
if "--processes" in sys.argv:
""" locusts -f locustfile.py --processes 4
"""
"""locusts -f locustfile.py --processes 4"""
if "--no-web" in sys.argv:
logger.log_error("conflict parameter args: --processes & --no-web. \nexit.")
sys.exit(1)
processes_index = sys.argv.index('--processes')
processes_index = sys.argv.index("--processes")
processes_count_index = processes_index + 1
if processes_count_index >= len(sys.argv):
""" do not specify processes count explicitly
locusts -f locustfile.py --processes
"""do not specify processes count explicitly
locusts -f locustfile.py --processes
"""
processes_count = multiprocessing.cpu_count()
logger.log_warning("processes count not specified, use {} by default.".format(processes_count))
logger.log_warning(
"processes count not specified, use {} by default.".format(
processes_count
)
)
else:
try:
""" locusts -f locustfile.py --processes 4 """
"""locusts -f locustfile.py --processes 4"""
processes_count = int(sys.argv[processes_count_index])
sys.argv.pop(processes_count_index)
except ValueError:
""" locusts -f locustfile.py --processes -P 8888 """
"""locusts -f locustfile.py --processes -P 8888"""
processes_count = multiprocessing.cpu_count()
logger.log_warning("processes count not specified, use {} by default.".format(processes_count))
logger.log_warning(
"processes count not specified, use {} by default.".format(
processes_count
)
)
sys.argv.pop(processes_index)
run_locusts_with_processes(sys.argv, processes_count)

View File

@@ -9,8 +9,8 @@ from httprunner.ext.locusts.utils import prepare_locust_tests
from httprunner.runner import Runner
logging.getLogger().setLevel(logging.CRITICAL)
logging.getLogger('locust.main').setLevel(logging.INFO)
logging.getLogger('locust.runners').setLevel(logging.INFO)
logging.getLogger("locust.main").setLevel(logging.INFO)
logging.getLogger("locust.runners").setLevel(logging.INFO)
class WebPageTasks(TaskSet):
@@ -28,7 +28,7 @@ class WebPageTasks(TaskSet):
request_type=self.test_runner.exception_request_type,
name=self.test_runner.exception_name,
response_time=0,
exception=ex
exception=ex,
)

View File

@@ -2,7 +2,7 @@ from httprunner import loader, parser
def prepare_locust_tests(path):
""" prepare locust testcases
"""prepare locust testcases
Args:
path (str): testcase file path.

View File

@@ -61,7 +61,7 @@ from httprunner.exceptions import ParamsError
def prepare_upload_test(test_dict):
""" preprocess for upload test
"""preprocess for upload test
replace `upload` info with MultipartEncoder
Args:
@@ -96,14 +96,15 @@ def prepare_upload_test(test_dict):
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
test_dict["request"].setdefault("headers", {})
test_dict["request"]["headers"]["Content-Type"] = "${multipart_content_type($m_encoder)}"
test_dict["request"]["headers"][
"Content-Type"
] = "${multipart_content_type($m_encoder)}"
test_dict["request"]["data"] = "$m_encoder"
def multipart_encoder(**kwargs):
""" initialize MultipartEncoder with uploading fields.
"""
"""initialize MultipartEncoder with uploading fields."""
def get_filetype(file_path):
file_type = filetype.guess(file_path)
@@ -122,6 +123,7 @@ def multipart_encoder(**kwargs):
else:
# value is not absolute file path, check if it is relative file path
from httprunner.loader import get_pwd
_file_path = os.path.join(get_pwd(), value)
is_exists_file = os.path.isfile(_file_path)
@@ -130,7 +132,7 @@ def multipart_encoder(**kwargs):
filename = os.path.basename(_file_path)
mime_type = get_filetype(_file_path)
# TODO: fix ResourceWarning for unclosed file
file_handler = open(_file_path, 'rb')
file_handler = open(_file_path, "rb")
fields_dict[key] = (filename, file_handler, mime_type)
else:
fields_dict[key] = value
@@ -139,6 +141,5 @@ def multipart_encoder(**kwargs):
def multipart_content_type(m_encoder):
""" prepare Content-Type for request headers
"""
"""prepare Content-Type for request headers"""
return m_encoder.content_type

View File

@@ -9,8 +9,10 @@ HttpRunner loader
"""
from httprunner.loader.check import is_test_path, is_test_content, JsonSchemaChecker
from httprunner.loader.locate import get_project_working_directory as get_pwd, \
init_project_working_directory as init_pwd
from httprunner.loader.locate import (
get_project_working_directory as get_pwd,
init_project_working_directory as init_pwd,
)
from httprunner.loader.load import load_csv_file, load_builtin_functions
from httprunner.loader.buildup import load_cases, load_project_data
@@ -23,5 +25,5 @@ __all__ = [
"load_csv_file",
"load_builtin_functions",
"load_project_data",
"load_cases"
"load_cases",
]

View File

@@ -4,18 +4,22 @@ import sys
from httprunner import exceptions, logger, utils
from httprunner.loader.check import JsonSchemaChecker
from httprunner.loader.load import load_module_functions, load_file, load_dot_env_file, \
load_folder_files
from httprunner.loader.locate import init_project_working_directory, get_project_working_directory
from httprunner.loader.load import (
load_module_functions,
load_file,
load_dot_env_file,
load_folder_files,
)
from httprunner.loader.locate import (
init_project_working_directory,
get_project_working_directory,
)
tests_def_mapping = {
"api": {},
"testcases": {}
}
tests_def_mapping = {"api": {}, "testcases": {}}
def load_debugtalk_functions():
""" load project debugtalk.py module functions
"""load project debugtalk.py module functions
debugtalk.py should be located in project working directory.
Returns:
@@ -27,8 +31,8 @@ def load_debugtalk_functions():
"""
# load debugtalk.py module
if sys.modules.get('debugtalk'):
imported_module = importlib.reload(sys.modules['debugtalk'])
if sys.modules.get("debugtalk"):
imported_module = importlib.reload(sys.modules["debugtalk"])
else:
imported_module = importlib.import_module("debugtalk")
@@ -36,7 +40,7 @@ def load_debugtalk_functions():
def __extend_with_api_ref(raw_testinfo):
""" extend with api reference
"""extend with api reference
Raises:
exceptions.ApiNotFound: api not found
@@ -68,17 +72,13 @@ def __extend_with_api_ref(raw_testinfo):
def __extend_with_testcase_ref(raw_testinfo):
""" extend with testcase reference
"""
"""extend with testcase reference"""
testcase_path = raw_testinfo["testcase"]
if testcase_path not in tests_def_mapping["testcases"]:
# make compatible with Windows/Linux
pwd = get_project_working_directory()
testcase_path = os.path.join(
pwd,
*testcase_path.split("/")
)
testcase_path = os.path.join(pwd, *testcase_path.split("/"))
loaded_testcase = load_file(testcase_path)
if isinstance(loaded_testcase, list):
@@ -89,7 +89,8 @@ def __extend_with_testcase_ref(raw_testinfo):
testcase_dict = load_testcase_v2(loaded_testcase)
else:
raise exceptions.FileFormatError(
"Invalid format testcase: {}".format(testcase_path))
"Invalid format testcase: {}".format(testcase_path)
)
tests_def_mapping["testcases"][testcase_path] = testcase_dict
else:
@@ -99,7 +100,7 @@ def __extend_with_testcase_ref(raw_testinfo):
def load_teststep(raw_testinfo):
""" load testcase step content.
"""load testcase step content.
teststep maybe defined directly, or reference api/testcase.
Args:
@@ -151,7 +152,7 @@ def load_teststep(raw_testinfo):
def load_testcase(raw_testcase):
""" load testcase with api/testcase references.
"""load testcase with api/testcase references.
Args:
raw_testcase (list): raw testcase content loaded from JSON/YAML file:
@@ -192,17 +193,16 @@ def load_testcase(raw_testcase):
tests.append(load_teststep(test_block))
else:
logger.log_warning(
"unexpected block key: {}. block key should only be 'config' or 'test'.".format(key)
"unexpected block key: {}. block key should only be 'config' or 'test'.".format(
key
)
)
return {
"config": config,
"teststeps": tests
}
return {"config": config, "teststeps": tests}
def load_testcase_v2(raw_testcase):
""" load testcase in format version 2.
"""load testcase in format version 2.
Args:
raw_testcase (dict): raw testcase content loaded from JSON/YAML file:
@@ -233,15 +233,12 @@ def load_testcase_v2(raw_testcase):
"""
JsonSchemaChecker.validate_testcase_v2_format(raw_testcase)
raw_teststeps = raw_testcase.pop("teststeps")
raw_testcase["teststeps"] = [
load_teststep(teststep)
for teststep in raw_teststeps
]
raw_testcase["teststeps"] = [load_teststep(teststep) for teststep in raw_teststeps]
return raw_testcase
def load_testsuite(raw_testsuite):
""" load testsuite with testcase references.
"""load testsuite with testcase references.
support two different formats.
Args:
@@ -315,7 +312,7 @@ def load_testsuite(raw_testsuite):
def load_test_file(path):
""" load test file, file maybe testcase/testsuite/api
"""load test file, file maybe testcase/testsuite/api
Args:
path (str): test file path
@@ -390,7 +387,7 @@ def load_test_file(path):
def load_project_data(test_path, dot_env_path=None):
""" load api, testcases, .env, debugtalk.py functions.
"""load api, testcases, .env, debugtalk.py functions.
api/testcases folder is relative to project_working_directory
Args:
@@ -402,7 +399,9 @@ def load_project_data(test_path, dot_env_path=None):
environments and debugtalk.py functions.
"""
debugtalk_path, project_working_directory = init_project_working_directory(test_path)
debugtalk_path, project_working_directory = init_project_working_directory(
test_path
)
project_mapping = {}
@@ -428,7 +427,7 @@ def load_project_data(test_path, dot_env_path=None):
def load_cases(path, dot_env_path=None):
""" load testcases from file path, extend and merge with api/testcase definitions.
"""load testcases from file path, extend and merge with api/testcase definitions.
Args:
path (str): testcase/testsuite file/foler path.
@@ -481,9 +480,7 @@ def load_cases(path, dot_env_path=None):
"""
tests_mapping = {
"project_mapping": load_project_data(path, dot_env_path)
}
tests_mapping = {"project_mapping": load_project_data(path, dot_env_path)}
def __load_file_content(path):
loaded_content = None

View File

@@ -15,12 +15,14 @@ testcase_schema_v2_path = os.path.join(schemas_root_dir, "testcase.schema.v2.jso
testsuite_schema_v1_path = os.path.join(schemas_root_dir, "testsuite.schema.v1.json")
testsuite_schema_v2_path = os.path.join(schemas_root_dir, "testsuite.schema.v2.json")
with io.open(api_schema_path, encoding='utf-8') as f:
with io.open(api_schema_path, encoding="utf-8") as f:
api_schema = json.load(f)
with io.open(common_schema_path, encoding='utf-8') as f:
with io.open(common_schema_path, encoding="utf-8") as f:
if platform.system() == "Windows":
absolute_base_path = 'file:///' + os.path.abspath(schemas_root_dir).replace("\\", "/") + '/'
absolute_base_path = (
"file:///" + os.path.abspath(schemas_root_dir).replace("\\", "/") + "/"
)
else:
# Linux, Darwin
absolute_base_path = "file://" + os.path.abspath(schemas_root_dir) + "/"
@@ -28,25 +30,23 @@ with io.open(common_schema_path, encoding='utf-8') as f:
common_schema = json.load(f)
resolver = jsonschema.RefResolver(absolute_base_path, common_schema)
with io.open(testcase_schema_v1_path, encoding='utf-8') as f:
with io.open(testcase_schema_v1_path, encoding="utf-8") as f:
testcase_schema_v1 = json.load(f)
with io.open(testcase_schema_v2_path, encoding='utf-8') as f:
with io.open(testcase_schema_v2_path, encoding="utf-8") as f:
testcase_schema_v2 = json.load(f)
with io.open(testsuite_schema_v1_path, encoding='utf-8') as f:
with io.open(testsuite_schema_v1_path, encoding="utf-8") as f:
testsuite_schema_v1 = json.load(f)
with io.open(testsuite_schema_v2_path, encoding='utf-8') as f:
with io.open(testsuite_schema_v2_path, encoding="utf-8") as f:
testsuite_schema_v2 = json.load(f)
class JsonSchemaChecker(object):
@staticmethod
def validate_format(content, scheme):
""" check api/testcase/testsuite format if valid
"""
"""check api/testcase/testsuite format if valid"""
try:
jsonschema.validate(content, scheme, resolver=resolver)
except jsonschema.exceptions.ValidationError as ex:
@@ -57,37 +57,32 @@ class JsonSchemaChecker(object):
@staticmethod
def validate_api_format(content):
""" check api format if valid
"""
"""check api format if valid"""
return JsonSchemaChecker.validate_format(content, api_schema)
@staticmethod
def validate_testcase_v1_format(content):
""" check testcase format v1 if valid
"""
"""check testcase format v1 if valid"""
return JsonSchemaChecker.validate_format(content, testcase_schema_v1)
@staticmethod
def validate_testcase_v2_format(content):
""" check testcase format v2 if valid
"""
"""check testcase format v2 if valid"""
return JsonSchemaChecker.validate_format(content, testcase_schema_v2)
@staticmethod
def validate_testsuite_v1_format(content):
""" check testsuite format v1 if valid
"""
"""check testsuite format v1 if valid"""
return JsonSchemaChecker.validate_format(content, testsuite_schema_v1)
@staticmethod
def validate_testsuite_v2_format(content):
""" check testsuite format v2 if valid
"""
"""check testsuite format v2 if valid"""
return JsonSchemaChecker.validate_format(content, testsuite_schema_v2)
def is_test_path(path):
""" check if path is valid json/yaml file path or a existed directory.
"""check if path is valid json/yaml file path or a existed directory.
Args:
path (str/list/tuple): file path/directory or file path list.
@@ -115,7 +110,7 @@ def is_test_path(path):
if os.path.isfile(path):
# path is a file
file_suffix = os.path.splitext(path)[1].lower()
if file_suffix not in ['.json', '.yaml', '.yml']:
if file_suffix not in [".json", ".yaml", ".yml"]:
# path is not json/yaml file
return False
else:
@@ -129,7 +124,7 @@ def is_test_path(path):
def is_test_content(data_structure):
""" check if data_structure is apis/testcases/testsuites.
"""check if data_structure is apis/testcases/testsuites.
Args:
data_structure (dict): should include keys, apis or testcases or testsuites

View File

@@ -13,15 +13,14 @@ from httprunner.loader.locate import get_project_working_directory
try:
# PyYAML version >= 5.1
# ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation
yaml.warnings({'YAMLLoadWarning': False})
yaml.warnings({"YAMLLoadWarning": False})
except AttributeError:
pass
def _load_yaml_file(yaml_file):
""" load yaml file and check file content format
"""
with io.open(yaml_file, 'r', encoding='utf-8') as stream:
"""load yaml file and check file content format"""
with io.open(yaml_file, "r", encoding="utf-8") as stream:
try:
yaml_content = yaml.load(stream)
except yaml.YAMLError as ex:
@@ -32,13 +31,12 @@ def _load_yaml_file(yaml_file):
def _load_json_file(json_file):
""" load json file and check file content format
"""
with io.open(json_file, encoding='utf-8') as data_file:
"""load json file and check file content format"""
with io.open(json_file, encoding="utf-8") as data_file:
try:
json_content = json.load(data_file)
except exceptions.JSONDecodeError:
err_msg = u"JSONDecodeError: JSON file format error: {}".format(json_file)
err_msg = "JSONDecodeError: JSON file format error: {}".format(json_file)
logger.log_error(err_msg)
raise exceptions.FileFormatError(err_msg)
@@ -46,7 +44,7 @@ def _load_json_file(json_file):
def load_csv_file(csv_file):
""" load csv file and check file content format
"""load csv file and check file content format
Args:
csv_file (str): csv file path, csv file content is like below:
@@ -80,7 +78,7 @@ def load_csv_file(csv_file):
csv_content_list = []
with io.open(csv_file, encoding='utf-8') as csvfile:
with io.open(csv_file, encoding="utf-8") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
csv_content_list.append(row)
@@ -93,21 +91,21 @@ def load_file(file_path):
raise exceptions.FileNotFound("{} does not exist.".format(file_path))
file_suffix = os.path.splitext(file_path)[1].lower()
if file_suffix == '.json':
if file_suffix == ".json":
return _load_json_file(file_path)
elif file_suffix in ['.yaml', '.yml']:
elif file_suffix in [".yaml", ".yml"]:
return _load_yaml_file(file_path)
elif file_suffix == ".csv":
return load_csv_file(file_path)
else:
# '' or other suffix
err_msg = u"Unsupported file format: {}".format(file_path)
err_msg = "Unsupported file format: {}".format(file_path)
logger.log_warning(err_msg)
return []
def load_folder_files(folder_path, recursive=True):
""" load folder path, return all files endswith yml/yaml/json in list.
"""load folder path, return all files endswith yml/yaml/json in list.
Args:
folder_path (str): specified folder path to load
@@ -132,7 +130,7 @@ def load_folder_files(folder_path, recursive=True):
filenames_list = []
for filename in filenames:
if not filename.endswith(('.yml', '.yaml', '.json')):
if not filename.endswith((".yml", ".yaml", ".json")):
continue
filenames_list.append(filename)
@@ -148,7 +146,7 @@ def load_folder_files(folder_path, recursive=True):
def load_dot_env_file(dot_env_path):
""" load .env file.
"""load .env file.
Args:
dot_env_path (str): .env file path
@@ -172,7 +170,7 @@ def load_dot_env_file(dot_env_path):
logger.log_info("Loading environment variables from {}".format(dot_env_path))
env_variables_mapping = {}
with io.open(dot_env_path, 'r', encoding='utf-8') as fp:
with io.open(dot_env_path, "r", encoding="utf-8") as fp:
for line in fp:
# maxsplit=1
if "=" in line:
@@ -189,7 +187,7 @@ def load_dot_env_file(dot_env_path):
def load_module_functions(module):
""" load python module functions.
"""load python module functions.
Args:
module: python module
@@ -213,7 +211,5 @@ def load_module_functions(module):
def load_builtin_functions():
""" load builtin module functions
"""
"""load builtin module functions"""
return load_module_functions(builtin)

View File

@@ -7,7 +7,7 @@ project_working_directory = None
def locate_file(start_path, file_name):
""" locate filename and return absolute file path.
"""locate filename and return absolute file path.
searching will be recursive upward until current working directory or system root dir.
Args:
@@ -34,21 +34,25 @@ def locate_file(start_path, file_name):
# current working directory
if os.path.abspath(start_dir_path) == os.getcwd():
raise exceptions.FileNotFound("{} not found in {}".format(file_name, start_path))
raise exceptions.FileNotFound(
"{} not found in {}".format(file_name, start_path)
)
# system root dir
# Windows, e.g. 'E:\\'
# Linux/Darwin, '/'
parent_dir = os.path.dirname(start_dir_path)
if parent_dir == start_dir_path:
raise exceptions.FileNotFound("{} not found in {}".format(file_name, start_path))
raise exceptions.FileNotFound(
"{} not found in {}".format(file_name, start_path)
)
# locate recursive upward
return locate_file(parent_dir, file_name)
def locate_debugtalk_py(start_path):
""" locate debugtalk.py file
"""locate debugtalk.py file
Args:
start_path (str): start locating path,
@@ -68,7 +72,7 @@ def locate_debugtalk_py(start_path):
def init_project_working_directory(test_path):
""" this should be called at startup
"""this should be called at startup
run test file:
run_path -> load_cases -> load_project_data -> init_project_working_directory

View File

@@ -11,11 +11,11 @@ LOG_LEVEL = "INFO"
LOG_FILE_PATH = ""
log_colors_config = {
'DEBUG': 'cyan',
'INFO': 'green',
'WARNING': 'yellow',
'ERROR': 'red',
'CRITICAL': 'red',
"DEBUG": "cyan",
"INFO": "green",
"WARNING": "yellow",
"ERROR": "red",
"CRITICAL": "red",
}
loggers = {}
@@ -58,10 +58,10 @@ def get_logger(name=None):
handler = logging.StreamHandler(sys.stdout)
formatter = ColoredFormatter(
u"%(log_color)s%(bg_white)s%(levelname)-8s%(reset)s %(message)s",
"%(log_color)s%(bg_white)s%(levelname)-8s%(reset)s %(message)s",
datefmt=None,
reset=True,
log_colors=log_colors_config
log_colors=log_colors_config,
)
handler.setFormatter(formatter)
_logger.addHandler(handler)
@@ -81,8 +81,8 @@ def color_print(msg, color="WHITE"):
def log_with_color(level):
""" log with color by different level
"""
"""log with color by different level"""
def wrapper(text):
color = log_colors_config[level.upper()]
_logger = get_logger()

View File

@@ -27,7 +27,7 @@ def get_parse_failed_testfiles():
def parse_string_value(str_value):
""" parse string to number if possible
"""parse string to number if possible
e.g. "123" => 123
"12.2" => 12.3
"abc" => "abc"
@@ -43,8 +43,7 @@ def parse_string_value(str_value):
def is_var_or_func_exist(content):
""" check if variable or function exist
"""
"""check if variable or function exist"""
if not isinstance(content, basestring):
return False
@@ -71,7 +70,7 @@ def is_var_or_func_exist(content):
def regex_findall_variables(content):
""" extract all variable names from content, which is in format $variable
"""extract all variable names from content, which is in format $variable
Args:
content (str): string content
@@ -96,16 +95,14 @@ def regex_findall_variables(content):
try:
vars_list = []
for var_tuple in variable_regex_compile.findall(content):
vars_list.append(
var_tuple[0] or var_tuple[1]
)
vars_list.append(var_tuple[0] or var_tuple[1])
return vars_list
except TypeError:
return []
def regex_findall_functions(content):
""" extract all functions from string content, which are in format ${fun()}
"""extract all functions from string content, which are in format ${fun()}
Args:
content (str): string content
@@ -137,7 +134,7 @@ def regex_findall_functions(content):
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None):
""" parse parameters and generate cartesian product.
"""parse parameters and generate cartesian product.
Args:
parameters (list) parameters: parameter name and value in list
@@ -188,13 +185,9 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
parameter_content_list.append(parameter_content_dict)
else:
# (2) & (3)
parsed_variables_mapping = parse_variables_mapping(
variables_mapping
)
parsed_variables_mapping = parse_variables_mapping(variables_mapping)
parsed_parameter_content = eval_lazy_data(
parameter_content,
parsed_variables_mapping,
functions_mapping
parameter_content, parsed_variables_mapping, functions_mapping
)
if not isinstance(parsed_parameter_content, list):
raise exceptions.ParamsError("parameters syntax error!")
@@ -210,7 +203,9 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
# {"username": "user1", "password": "111111"},
# {"username": "user2", "password": "222222"}
# ]
parameter_dict = {key: parameter_item[key] for key in parameter_name_list}
parameter_dict = {
key: parameter_item[key] for key in parameter_name_list
}
elif isinstance(parameter_item, (list, tuple)):
# {"username-password": "${get_account()}"}
# get_account() => [("user1", "111111"), ("user2", "222222")]
@@ -218,9 +213,7 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
elif len(parameter_name_list) == 1:
# {"user_agent": "${get_user_agent()}"}
# get_user_agent() => ["iOS/10.1", "iOS/10.2"]
parameter_dict = {
parameter_name_list[0]: parameter_item
}
parameter_dict = {parameter_name_list[0]: parameter_item}
parameter_content_list.append(parameter_dict)
@@ -230,8 +223,7 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
def get_uniform_comparator(comparator):
""" convert comparator alias to uniform name
"""
"""convert comparator alias to uniform name"""
if comparator in ["eq", "equals", "==", "is"]:
return "equals"
elif comparator in ["lt", "less_than"]:
@@ -248,22 +240,35 @@ def get_uniform_comparator(comparator):
return "string_equals"
elif comparator in ["len_eq", "length_equals", "count_eq"]:
return "length_equals"
elif comparator in ["len_gt", "count_gt", "length_greater_than", "count_greater_than"]:
elif comparator in [
"len_gt",
"count_gt",
"length_greater_than",
"count_greater_than",
]:
return "length_greater_than"
elif comparator in ["len_ge", "count_ge", "length_greater_than_or_equals",
"count_greater_than_or_equals"]:
elif comparator in [
"len_ge",
"count_ge",
"length_greater_than_or_equals",
"count_greater_than_or_equals",
]:
return "length_greater_than_or_equals"
elif comparator in ["len_lt", "count_lt", "length_less_than", "count_less_than"]:
return "length_less_than"
elif comparator in ["len_le", "count_le", "length_less_than_or_equals",
"count_less_than_or_equals"]:
elif comparator in [
"len_le",
"count_le",
"length_less_than_or_equals",
"count_less_than_or_equals",
]:
return "length_less_than_or_equals"
else:
return comparator
def uniform_validator(validator):
""" unify validator
"""unify validator
Args:
validator (dict): validator maybe in two formats:
@@ -310,15 +315,11 @@ def uniform_validator(validator):
# uniform comparator, e.g. lt => less_than, eq => equals
comparator = get_uniform_comparator(comparator)
return {
"check": check_item,
"expect": expect_value,
"comparator": comparator
}
return {"check": check_item, "expect": expect_value, "comparator": comparator}
def _convert_validators_to_mapping(validators):
""" convert validators list to mapping.
"""convert validators list to mapping.
Args:
validators (list): validators in list
@@ -353,7 +354,7 @@ def _convert_validators_to_mapping(validators):
def extend_validators(raw_validators, override_validators):
""" extend raw_validators with override_validators.
"""extend raw_validators with override_validators.
override_validators will merge and override raw_validators.
Args:
@@ -393,8 +394,9 @@ def extend_validators(raw_validators, override_validators):
## parse content with variables and functions mapping
###############################################################################
def get_mapping_variable(variable_name, variables_mapping):
""" get variable from variables_mapping.
"""get variable from variables_mapping.
Args:
variable_name (str): variable name
@@ -414,7 +416,7 @@ def get_mapping_variable(variable_name, variables_mapping):
def get_mapping_function(function_name, functions_mapping):
""" get function from functions_mapping,
"""get function from functions_mapping,
if not found, then try to check if builtin function.
Args:
@@ -440,6 +442,7 @@ def get_mapping_function(function_name, functions_mapping):
elif function_name in ["multipart_encoder", "multipart_content_type"]:
# extension for upload test
from httprunner.ext import uploader
return getattr(uploader, function_name)
try:
@@ -459,7 +462,7 @@ def get_mapping_function(function_name, functions_mapping):
def parse_function_params(params):
""" parse function params to args and kwargs.
"""parse function params to args and kwargs.
Args:
params (str): function param in string
@@ -489,20 +492,17 @@ def parse_function_params(params):
{'args': [1, 2], 'kwargs': {'a':3, 'b':4}}
"""
function_meta = {
"args": [],
"kwargs": {}
}
function_meta = {"args": [], "kwargs": {}}
params_str = params.strip()
if params_str == "":
return function_meta
args_list = params_str.split(',')
args_list = params_str.split(",")
for arg in args_list:
arg = arg.strip()
if '=' in arg:
key, value = arg.split('=')
if "=" in arg:
key, value = arg.split("=")
function_meta["kwargs"][key.strip()] = parse_string_value(value.strip())
else:
function_meta["args"].append(parse_string_value(arg))
@@ -511,11 +511,10 @@ def parse_function_params(params):
class LazyFunction(object):
""" call function lazily.
"""
"""call function lazily."""
def __init__(self, function_meta, functions_mapping=None, check_variables_set=None):
""" init LazyFunction object with function_meta
"""init LazyFunction object with function_meta
Args:
function_meta (dict): function name, args and kwargs.
@@ -532,25 +531,24 @@ class LazyFunction(object):
self.__parse(function_meta)
def __parse(self, function_meta):
""" init func as lazy functon instance
"""init func as lazy functon instance
Args:
function_meta (dict): function meta including name, args and kwargs
"""
self._func = get_mapping_function(
function_meta["func_name"],
self.functions_mapping
function_meta["func_name"], self.functions_mapping
)
self.func_name = self._func.__name__
self._args = prepare_lazy_data(
function_meta.get("args", []),
self.functions_mapping,
self.check_variables_set
self.check_variables_set,
)
self._kwargs = prepare_lazy_data(
function_meta.get("kwargs", {}),
self.functions_mapping,
self.check_variables_set
self.check_variables_set,
)
if self.func_name == "load_csv_file":
@@ -578,8 +576,7 @@ class LazyFunction(object):
if self._kwargs:
args_string += ", "
str_kwargs = [
"{}={}".format(key, str(value))
for key, value in self._kwargs.items()
"{}={}".format(key, str(value)) for key, value in self._kwargs.items()
]
args_string += ", ".join(str_kwargs)
@@ -589,8 +586,8 @@ class LazyFunction(object):
return self.func_name, repr(args), repr(kwargs)
def to_value(self, variables_mapping=None):
""" parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""
variables_mapping = variables_mapping or {}
args = parse_lazy_data(self._args, variables_mapping)
@@ -605,12 +602,13 @@ cached_functions_mapping = {}
class LazyString(object):
""" evaluate string lazily.
"""
"""evaluate string lazily."""
def __init__(self, raw_string, functions_mapping=None, check_variables_set=None, cached=False):
""" make raw_string as lazy object with functions_mapping
check if any variable undefined in check_variables_set
def __init__(
self, raw_string, functions_mapping=None, check_variables_set=None, cached=False
):
"""make raw_string as lazy object with functions_mapping
check if any variable undefined in check_variables_set
"""
self.raw_string = raw_string
self.functions_mapping = functions_mapping or {}
@@ -619,7 +617,7 @@ class LazyString(object):
self.__parse(raw_string)
def __parse(self, raw_string):
""" parse raw string, replace function and variable with {}
"""parse raw string, replace function and variable with {}
Args:
raw_string(str): string with functions or varialbes
@@ -658,14 +656,10 @@ class LazyString(object):
# search function like ${func($a, $b)}
func_match = function_regex_compile.match(raw_string, match_start_position)
if func_match:
function_meta = {
"func_name": func_match.group(1)
}
function_meta = {"func_name": func_match.group(1)}
function_meta.update(parse_function_params(func_match.group(2)))
lazy_func = LazyFunction(
function_meta,
self.functions_mapping,
self.check_variables_set
function_meta, self.functions_mapping, self.check_variables_set
)
self._args.append(lazy_func)
match_start_position = func_match.end()
@@ -701,15 +695,19 @@ class LazyString(object):
return "LazyString({})".format(self.raw_string)
def to_value(self, variables_mapping=None):
""" parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""
variables_mapping = variables_mapping or {}
args = []
for arg in self._args:
if isinstance(arg, LazyFunction):
if self.cached and arg.cache_key and arg.cache_key in cached_functions_mapping:
if (
self.cached
and arg.cache_key
and arg.cache_key in cached_functions_mapping
):
value = cached_functions_mapping[arg.cache_key]
else:
value = arg.to_value(variables_mapping)
@@ -726,8 +724,10 @@ class LazyString(object):
return self._string.format(*args)
def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None, cached=False):
""" make string in content as lazy object with functions_mapping
def prepare_lazy_data(
content, functions_mapping=None, check_variables_set=None, cached=False
):
"""make string in content as lazy object with functions_mapping
Raises:
exceptions.VariableNotFound: if any variable undefined in check_variables_set
@@ -739,12 +739,7 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,
elif isinstance(content, (list, set, tuple)):
return [
prepare_lazy_data(
item,
functions_mapping,
check_variables_set,
cached
)
prepare_lazy_data(item, functions_mapping, check_variables_set, cached)
for item in content
]
@@ -752,16 +747,10 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,
parsed_content = {}
for key, value in content.items():
parsed_key = prepare_lazy_data(
key,
functions_mapping,
check_variables_set,
cached
key, functions_mapping, check_variables_set, cached
)
parsed_value = prepare_lazy_data(
value,
functions_mapping,
check_variables_set,
cached
value, functions_mapping, check_variables_set, cached
)
parsed_content[parsed_key] = parsed_value
@@ -783,8 +772,8 @@ def prepare_lazy_data(content, functions_mapping=None, check_variables_set=None,
def parse_lazy_data(content, variables_mapping=None):
""" parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""parse lazy data with evaluated variables mapping.
Notice: variables_mapping should not contain any variable or function.
"""
# TODO: refactor type check
if content is None or isinstance(content, (numeric_types, bool, type)):
@@ -795,10 +784,7 @@ def parse_lazy_data(content, variables_mapping=None):
return content.to_value(variables_mapping)
elif isinstance(content, (list, set, tuple)):
return [
parse_lazy_data(item, variables_mapping)
for item in content
]
return [parse_lazy_data(item, variables_mapping) for item in content]
elif isinstance(content, dict):
parsed_content = {}
@@ -813,24 +799,19 @@ def parse_lazy_data(content, variables_mapping=None):
def eval_lazy_data(content, variables_mapping=None, functions_mapping=None):
""" evaluate data instantly.
Notice: variables_mapping should not contain any variable or function.
"""evaluate data instantly.
Notice: variables_mapping should not contain any variable or function.
"""
variables_mapping = variables_mapping or {}
check_variables_set = set(variables_mapping.keys())
return parse_lazy_data(
prepare_lazy_data(
content,
functions_mapping,
check_variables_set
),
variables_mapping
prepare_lazy_data(content, functions_mapping, check_variables_set),
variables_mapping,
)
def extract_variables(content):
""" extract all variables in content recursively.
"""
"""extract all variables in content recursively."""
if isinstance(content, (list, set, tuple)):
variables = set()
for item in content:
@@ -850,7 +831,7 @@ def extract_variables(content):
def parse_variables_mapping(variables_mapping):
""" eval each prepared variable and function in variables_mapping.
"""eval each prepared variable and function in variables_mapping.
Args:
variables_mapping (dict):
@@ -911,7 +892,12 @@ def parse_variables_mapping(variables_mapping):
# reference other variable, or function call with other variable
# e.g. {"varA": "123$varB", "varB": "456$varC"}
# e.g. {"varC": "${sum_two($a, $b)}"}
if any([_var_name not in parsed_variables_mapping for _var_name in variables]):
if any(
[
_var_name not in parsed_variables_mapping
for _var_name in variables
]
):
# reference variable not parsed
continue
@@ -922,7 +908,7 @@ def parse_variables_mapping(variables_mapping):
def _extend_with_api(test_dict, api_def_dict):
""" extend test with api definition, test will merge and override api definition.
"""extend test with api definition, test will merge and override api definition.
Args:
test_dict (dict): test block, this will override api_def_dict
@@ -955,27 +941,21 @@ def _extend_with_api(test_dict, api_def_dict):
# override variables
def_variables = api_def_dict.pop("variables", [])
test_dict["variables"] = utils.extend_variables(
def_variables,
test_dict.get("variables", {})
def_variables, test_dict.get("variables", {})
)
# merge & override validators TODO: relocate
def_raw_validators = api_def_dict.pop("validate", [])
def_validators = [
uniform_validator(_validator)
for _validator in def_raw_validators
uniform_validator(_validator) for _validator in def_raw_validators
]
ref_validators = test_dict.pop("validate", [])
test_dict["validate"] = extend_validators(
def_validators,
ref_validators
)
test_dict["validate"] = extend_validators(def_validators, ref_validators)
# merge & override extractors
def_extrators = api_def_dict.pop("extract", {})
test_dict["extract"] = utils.extend_variables(
def_extrators,
test_dict.get("extract", {})
def_extrators, test_dict.get("extract", {})
)
# merge & override request
@@ -1010,7 +990,7 @@ def _extend_with_api(test_dict, api_def_dict):
def _extend_with_testcase(test_dict, testcase_def_dict):
""" extend test with testcase definition
"""extend test with testcase definition
test will merge and override testcase config definition.
Args:
@@ -1024,7 +1004,8 @@ def _extend_with_testcase(test_dict, testcase_def_dict):
# override testcase config variables
testcase_def_dict["config"].setdefault("variables", {})
testcase_def_variables = utils.ensure_mapping_format(
testcase_def_dict["config"].get("variables", {}))
testcase_def_dict["config"].get("variables", {})
)
testcase_def_variables.update(test_dict.pop("variables", {}))
testcase_def_dict["config"]["variables"] = testcase_def_variables
@@ -1035,9 +1016,11 @@ def _extend_with_testcase(test_dict, testcase_def_dict):
testcase_def_dict["config"]["base_url"] = test_base_url
# override name
test_name = test_dict.pop("name", None) \
or testcase_def_dict["config"].pop("name", None) \
or "testcase name undefined"
test_name = (
test_dict.pop("name", None)
or testcase_def_dict["config"].pop("name", None)
or "testcase name undefined"
)
# override testcase config name, output, etc.
testcase_def_dict["config"].update(test_dict)
@@ -1048,8 +1031,7 @@ def _extend_with_testcase(test_dict, testcase_def_dict):
def __prepare_config(config, project_mapping, session_variables_set=None):
""" parse testcase/testsuite config.
"""
"""parse testcase/testsuite config."""
# get config variables
raw_config_variables = config.pop("variables", {})
@@ -1057,7 +1039,8 @@ def __prepare_config(config, project_mapping, session_variables_set=None):
functions = project_mapping.get("functions", {})
if isinstance(raw_config_variables, basestring) and function_regex_compile.match(
raw_config_variables):
raw_config_variables
):
# config variables are generated by calling function
# e.g.
# "config": {
@@ -1077,13 +1060,17 @@ def __prepare_config(config, project_mapping, session_variables_set=None):
config["variables"] = raw_config_variables_mapping
check_variables_set = set(raw_config_variables_mapping.keys())
check_variables_set |= (session_variables_set or set())
prepared_config = prepare_lazy_data(config, functions, check_variables_set, cached=True)
check_variables_set |= session_variables_set or set()
prepared_config = prepare_lazy_data(
config, functions, check_variables_set, cached=True
)
return prepared_config
def __prepare_testcase_tests(tests, config, project_mapping, session_variables_set=None):
""" override tests with testcase config variables, base_url and verify.
def __prepare_testcase_tests(
tests, config, project_mapping, session_variables_set=None
):
"""override tests with testcase config variables, base_url and verify.
test maybe nested testcase.
variables priority:
@@ -1107,7 +1094,9 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
functions = project_mapping.get("functions", {})
prepared_testcase_tests = []
session_variables_set = set(config_variables.keys()) | (session_variables_set or set())
session_variables_set = set(config_variables.keys()) | (
session_variables_set or set()
)
for test_dict in tests:
teststep_variables_set = {"request", "response"}
@@ -1115,8 +1104,7 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
# 1, testcase config => testcase tests
# override test_dict variables
test_dict_variables = utils.extend_variables(
test_dict.pop("variables", {}),
config_variables
test_dict.pop("variables", {}), config_variables
)
test_dict["variables"] = test_dict_variables
@@ -1128,8 +1116,7 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
if "validate" in test_dict:
ref_raw_validators = test_dict.pop("validate", [])
test_dict["validate"] = [
uniform_validator(_validator)
for _validator in ref_raw_validators
uniform_validator(_validator) for _validator in ref_raw_validators
]
if "testcase_def" in test_dict:
@@ -1152,7 +1139,9 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
test_dict["config"].setdefault("verify", config_verify)
# 3, testcase_def config => testcase_def test_dict
test_dict = _parse_testcase(test_dict, project_mapping, session_variables_set)
test_dict = _parse_testcase(
test_dict, project_mapping, session_variables_set
)
if not test_dict:
continue
@@ -1169,6 +1158,7 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
if "upload" in test_dict["request"]:
from httprunner.ext.uploader import prepare_upload_test
prepare_upload_test(test_dict)
# current teststep variables
@@ -1187,27 +1177,18 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
for _validator in validators:
function_meta = {
"func_name": _validator["comparator"],
"args": [
_validator["check"],
_validator["expect"]
],
"kwargs": {}
"args": [_validator["check"], _validator["expect"]],
"kwargs": {},
}
prepared_validators.append(
LazyFunction(
function_meta,
functions,
teststep_variables_set
)
LazyFunction(function_meta, functions, teststep_variables_set)
)
test_dict["validate"] = prepared_validators
# convert variables and functions to lazy object.
# raises VariableNotFound if undefined variable exists in test_dict
prepared_test_dict = prepare_lazy_data(
test_dict,
functions,
teststep_variables_set
test_dict, functions, teststep_variables_set
)
prepared_testcase_tests.append(prepared_test_dict)
@@ -1215,7 +1196,7 @@ def __prepare_testcase_tests(tests, config, project_mapping, session_variables_s
def _parse_testcase(testcase, project_mapping, session_variables_set=None):
""" parse testcase
"""parse testcase
Args:
testcase (dict):
@@ -1229,25 +1210,22 @@ def _parse_testcase(testcase, project_mapping, session_variables_set=None):
try:
prepared_config = __prepare_config(
testcase["config"],
project_mapping,
session_variables_set
testcase["config"], project_mapping, session_variables_set
)
prepared_testcase_tests = __prepare_testcase_tests(
testcase["teststeps"],
prepared_config,
project_mapping,
session_variables_set
session_variables_set,
)
return {
"config": prepared_config,
"teststeps": prepared_testcase_tests
}
return {"config": prepared_config, "teststeps": prepared_testcase_tests}
except (exceptions.MyBaseFailure, exceptions.MyBaseError) as ex:
testcase_type = testcase["type"]
testcase_path = testcase.get("path")
logger.log_error("failed to parse testcase: {}, error: {}".format(testcase_path, ex))
logger.log_error(
"failed to parse testcase: {}, error: {}".format(testcase_path, ex)
)
global parse_failed_testfiles
if testcase_type not in parse_failed_testfiles:
@@ -1259,7 +1237,7 @@ def _parse_testcase(testcase, project_mapping, session_variables_set=None):
def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mapping):
""" override testscases with testsuite config variables, base_url and verify.
"""override testscases with testsuite config variables, base_url and verify.
variables priority:
parameters > testsuite config > testcase config > testcase_def config > testcase_def tests > api
@@ -1323,45 +1301,46 @@ def __get_parsed_testsuite_testcases(testcases, testsuite_config, project_mappin
# 1, testsuite config => testcase config
# override test_dict variables
testcase_config_variables = utils.extend_variables(
testcase.pop("variables", {}),
testsuite_config_variables
testcase.pop("variables", {}), testsuite_config_variables
)
# 2, testcase config > testcase_def config
# override testcase_def config variables
overrided_testcase_config_variables = utils.extend_variables(
parsed_testcase["config"].pop("variables", {}),
testcase_config_variables
parsed_testcase["config"].pop("variables", {}), testcase_config_variables
)
if overrided_testcase_config_variables:
parsed_testcase["config"]["variables"] = overrided_testcase_config_variables
# parse config variables
parsed_config_variables = parse_variables_mapping(overrided_testcase_config_variables)
parsed_config_variables = parse_variables_mapping(
overrided_testcase_config_variables
)
# parse parameters
if "parameters" in testcase and testcase["parameters"]:
cartesian_product_parameters = parse_parameters(
testcase["parameters"],
parsed_config_variables,
functions
testcase["parameters"], parsed_config_variables, functions
)
for parameter_variables in cartesian_product_parameters:
# deepcopy to avoid influence between parameters
testcase_copied = utils.deepcopy_dict(parsed_testcase)
parsed_config_variables_copied = utils.deepcopy_dict(parsed_config_variables)
testcase_copied["config"]["variables"] = utils.extend_variables(
parsed_config_variables_copied,
parameter_variables
parsed_config_variables_copied = utils.deepcopy_dict(
parsed_config_variables
)
testcase_copied["config"]["variables"] = utils.extend_variables(
parsed_config_variables_copied, parameter_variables
)
parsed_testcase_copied = _parse_testcase(
testcase_copied, project_mapping
)
parsed_testcase_copied = _parse_testcase(testcase_copied, project_mapping)
if not parsed_testcase_copied:
continue
parsed_testcase_copied["config"]["name"] = parse_lazy_data(
parsed_testcase_copied["config"]["name"],
testcase_copied["config"]["variables"]
testcase_copied["config"]["variables"],
)
parsed_testcase_list.append(parsed_testcase_copied)
@@ -1378,15 +1357,13 @@ def _parse_testsuite(testsuite, project_mapping):
testsuite.setdefault("config", {})
prepared_config = __prepare_config(testsuite["config"], project_mapping)
parsed_testcase_list = __get_parsed_testsuite_testcases(
testsuite["testcases"],
prepared_config,
project_mapping
testsuite["testcases"], prepared_config, project_mapping
)
return parsed_testcase_list
def parse_tests(tests_mapping):
""" parse tests and load to parsed testcases
"""parse tests and load to parsed testcases
tests include api, testcases and testsuites.
Args:
@@ -1475,12 +1452,10 @@ def parse_tests(tests_mapping):
# encapsulate api as a testcase
for api_content in tests_mapping["apis"]:
testcase = {
"config": {
"name": api_content.get("name")
},
"config": {"name": api_content.get("name")},
"teststeps": [api_content],
"path": api_content.pop("path", None),
"type": api_content.pop("type", "api")
"type": api_content.pop("type", "api"),
}
parsed_testcase = _parse_testcase(testcase, project_mapping)
if not parsed_testcase:

View File

@@ -16,5 +16,5 @@ __all__ = [
"get_summary",
"stringify_summary",
"HtmlTestResult",
"gen_html_report"
"gen_html_report",
]

View File

@@ -9,7 +9,4 @@ HttpRunner html report
from httprunner.report.html.result import HtmlTestResult
from httprunner.report.html.gen_report import gen_html_report
__all__ = [
"HtmlTestResult",
"gen_html_report"
]
__all__ = ["HtmlTestResult", "gen_html_report"]

View File

@@ -9,7 +9,7 @@ from httprunner.exceptions import SummaryEmpty
def gen_html_report(summary, report_template=None, report_dir=None, report_file=None):
""" render html report with specified report name and template
"""render html report with specified report name and template
Args:
summary (dict): test result summary data
@@ -24,8 +24,7 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
if not report_template:
report_template = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"template.html"
os.path.abspath(os.path.dirname(__file__)), "template.html"
)
logger.log_debug("No html report template specified, use default.")
else:
@@ -43,22 +42,22 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
else:
report_dir = report_dir or os.path.join(os.getcwd(), "reports")
# fix #826: Windows does not support file name include ":"
report_file_name = "{}.html".format(utc_time_iso_8601_str.replace(":", "").replace("-", ""))
report_file_name = "{}.html".format(
utc_time_iso_8601_str.replace(":", "").replace("-", "")
)
if not os.path.isdir(report_dir):
os.makedirs(report_dir)
report_path = os.path.join(report_dir, report_file_name)
with io.open(report_template, "r", encoding='utf-8') as fp_r:
with io.open(report_template, "r", encoding="utf-8") as fp_r:
template_content = fp_r.read()
with io.open(report_path, 'w', encoding='utf-8') as fp_w:
with io.open(report_path, "w", encoding="utf-8") as fp_w:
rendered_content = Template(
template_content,
extensions=["jinja2.ext.loopcontrols"]
template_content, extensions=["jinja2.ext.loopcontrols"]
).render(summary)
fp_w.write(rendered_content)
logger.log_info("Generated Html report: {}".format(report_path))
return report_path

View File

@@ -5,19 +5,20 @@ from httprunner import logger
class HtmlTestResult(unittest.TextTestResult):
""" A html result class that can generate formatted html results.
Used by TextTestRunner.
"""A html result class that can generate formatted html results.
Used by TextTestRunner.
"""
def __init__(self, stream, descriptions, verbosity):
super(HtmlTestResult, self).__init__(stream, descriptions, verbosity)
self.records = []
def _record_test(self, test, status, attachment=''):
def _record_test(self, test, status, attachment=""):
data = {
'name': test.shortDescription(),
'status': status,
'attachment': attachment,
"meta_datas": test.meta_datas
"name": test.shortDescription(),
"status": status,
"attachment": attachment,
"meta_datas": test.meta_datas,
}
self.records.append(data)
@@ -25,38 +26,38 @@ class HtmlTestResult(unittest.TextTestResult):
self.start_at = time.time()
def startTest(self, test):
""" add start test time """
"""add start test time"""
super(HtmlTestResult, self).startTest(test)
logger.color_print(test.shortDescription(), "yellow")
def addSuccess(self, test):
super(HtmlTestResult, self).addSuccess(test)
self._record_test(test, 'success')
self._record_test(test, "success")
print("")
def addError(self, test, err):
super(HtmlTestResult, self).addError(test, err)
self._record_test(test, 'error', self._exc_info_to_string(err, test))
self._record_test(test, "error", self._exc_info_to_string(err, test))
print("")
def addFailure(self, test, err):
super(HtmlTestResult, self).addFailure(test, err)
self._record_test(test, 'failure', self._exc_info_to_string(err, test))
self._record_test(test, "failure", self._exc_info_to_string(err, test))
print("")
def addSkip(self, test, reason):
super(HtmlTestResult, self).addSkip(test, reason)
self._record_test(test, 'skipped', reason)
self._record_test(test, "skipped", reason)
print("")
def addExpectedFailure(self, test, err):
super(HtmlTestResult, self).addExpectedFailure(test, err)
self._record_test(test, 'ExpectedFailure', self._exc_info_to_string(err, test))
self._record_test(test, "ExpectedFailure", self._exc_info_to_string(err, test))
print("")
def addUnexpectedSuccess(self, test):
super(HtmlTestResult, self).addUnexpectedSuccess(test)
self._record_test(test, 'UnexpectedSuccess')
self._record_test(test, "UnexpectedSuccess")
print("")
@property

View File

@@ -9,43 +9,37 @@ from httprunner import __version__
def prepare_event_kwargs(event_name, params):
""" prepare report event kwargs"""
"""prepare report event kwargs"""
kwargs = {
"headers": {
'content-type': 'application/json'
},
"headers": {"content-type": "application/json"},
"json": {
"user": {
"user_unique_id": str(uuid.getnode())
},
"user": {"user_unique_id": str(uuid.getnode())},
"header": {
"app_id": 173519,
"os_name": platform.system(),
"os_version": platform.release(),
"app_version": __version__ # HttpRunner version
"app_version": __version__, # HttpRunner version
},
"events": [
{
"event": event_name,
"params": json.dumps(params),
"time": int(time.time())
"time": int(time.time()),
}
],
"verbose": 1
}
"verbose": 1,
},
}
return kwargs
def report_event(event_name, success=True):
params = {
"success": 1 if success else 0
}
params = {"success": 1 if success else 0}
kwargs = prepare_event_kwargs(event_name, params)
resp = requests.post("http://mcs.snssdk.com/v1/json", **kwargs)
print("resp---", resp.json())
if __name__ == '__main__':
if __name__ == "__main__":
report_event("loader")

View File

@@ -8,7 +8,7 @@ from httprunner.compat import basestring, bytes, json, numeric_types, JSONDecode
def dumps_json(value):
""" dumps json value to indented string
"""dumps json value to indented string
Args:
value (dict): raw json data
@@ -28,7 +28,7 @@ def detect_encoding(value):
def __stringify_request(request_data):
""" stringfy HTTP request data
"""stringfy HTTP request data
Args:
request_data (dict): HTTP request data in dict.
@@ -84,7 +84,7 @@ def __stringify_request(request_data):
def __stringify_response(response_data):
""" stringfy HTTP response data
"""stringfy HTTP response data
Args:
response_data (dict):
@@ -124,8 +124,7 @@ def __stringify_response(response_data):
if key == "body" and "image" in response_data["content_type"]:
# display image
value = "data:{};base64,{}".format(
response_data["content_type"],
b64encode(value).decode(encoding)
response_data["content_type"], b64encode(value).decode(encoding)
)
else:
value = escape(value.decode(encoding))
@@ -143,7 +142,7 @@ def __stringify_response(response_data):
def __expand_meta_datas(meta_datas, meta_datas_expanded):
""" expand meta_datas to one level
"""expand meta_datas to one level
Args:
meta_datas (dict/list): maybe in nested format
@@ -173,8 +172,7 @@ def __expand_meta_datas(meta_datas, meta_datas_expanded):
def __get_total_response_time(meta_datas_expanded):
""" caculate total response time of all meta_datas
"""
"""caculate total response time of all meta_datas"""
try:
response_time = 0
for meta_data in meta_datas_expanded:
@@ -200,15 +198,14 @@ def __stringify_meta_datas(meta_datas):
def stringify_summary(summary):
""" stringify summary, in order to dump json file and generate html report.
"""
"""stringify summary, in order to dump json file and generate html report."""
for index, suite_summary in enumerate(summary["details"]):
if not suite_summary.get("name"):
suite_summary["name"] = "testcase {}".format(index)
for record in suite_summary.get("records"):
meta_datas = record['meta_datas']
meta_datas = record["meta_datas"]
__stringify_meta_datas(meta_datas)
meta_datas_expanded = []
__expand_meta_datas(meta_datas, meta_datas_expanded)

View File

@@ -7,15 +7,14 @@ def get_platform():
return {
"httprunner_version": __version__,
"python_version": "{} {}".format(
platform.python_implementation(),
platform.python_version()
platform.python_implementation(), platform.python_version()
),
"platform": platform.platform()
"platform": platform.platform(),
}
def aggregate_stat(origin_stat, new_stat):
""" aggregate new_stat to origin_stat.
"""aggregate new_stat to origin_stat.
Args:
origin_stat (dict): origin stat dict, will be updated with new_stat dict.
@@ -30,8 +29,10 @@ def aggregate_stat(origin_stat, new_stat):
origin_stat["start_at"] = min(origin_stat["start_at"], new_stat["start_at"])
elif key == "duration":
# duration = max_end_time - min_start_time
max_end_time = max(origin_stat["start_at"] + origin_stat["duration"],
new_stat["start_at"] + new_stat["duration"])
max_end_time = max(
origin_stat["start_at"] + origin_stat["duration"],
new_stat["start_at"] + new_stat["duration"],
)
min_start_time = min(origin_stat["start_at"], new_stat["start_at"])
origin_stat["duration"] = max_end_time - min_start_time
else:
@@ -39,7 +40,7 @@ def aggregate_stat(origin_stat, new_stat):
def get_summary(result):
""" get summary from test result
"""get summary from test result
Args:
result (instance): HtmlTestResult() instance
@@ -58,25 +59,24 @@ def get_summary(result):
summary = {
"success": result.wasSuccessful(),
"stat": {
'total': result.testsRun,
'failures': len(result.failures),
'errors': len(result.errors),
'skipped': len(result.skipped),
'expectedFailures': len(result.expectedFailures),
'unexpectedSuccesses': len(result.unexpectedSuccesses)
}
"total": result.testsRun,
"failures": len(result.failures),
"errors": len(result.errors),
"skipped": len(result.skipped),
"expectedFailures": len(result.expectedFailures),
"unexpectedSuccesses": len(result.unexpectedSuccesses),
},
}
summary["stat"]["successes"] = summary["stat"]["total"] \
- summary["stat"]["failures"] \
- summary["stat"]["errors"] \
- summary["stat"]["skipped"] \
- summary["stat"]["expectedFailures"] \
- summary["stat"]["unexpectedSuccesses"]
summary["stat"]["successes"] = (
summary["stat"]["total"]
- summary["stat"]["failures"]
- summary["stat"]["errors"]
- summary["stat"]["skipped"]
- summary["stat"]["expectedFailures"]
- summary["stat"]["unexpectedSuccesses"]
)
summary["time"] = {
'start_at': result.start_at,
'duration': result.duration
}
summary["time"] = {"start_at": result.start_at, "duration": result.duration}
summary["records"] = result.records
return summary

View File

@@ -10,9 +10,8 @@ text_extractor_regexp_compile = re.compile(r".*\(.*\).*")
class ResponseObject(object):
def __init__(self, resp_obj):
""" initialize with a requests.Response object
"""initialize with a requests.Response object
Args:
resp_obj (instance): requests.Response instance
@@ -64,9 +63,9 @@ class ResponseObject(object):
return result
else:
raise exceptions.ExtractFailure("\tjsonpath {} get nothing\n".format(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.
Args:
@@ -87,15 +86,15 @@ class ResponseObject(object):
"""
matched = re.search(field, self.text)
if not matched:
err_msg = u"Failed to extract data with regex! => {}\n".format(field)
err_msg += u"response body: {}\n".format(self.text)
err_msg = "Failed to extract data with regex! => {}\n".format(field)
err_msg += "response body: {}\n".format(self.text)
logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg)
return matched.group(1)
def _extract_field_with_delimiter(self, field):
""" response content could be json or html text.
"""response content could be json or html text.
Args:
field (str): string joined by delimiter.
@@ -111,7 +110,7 @@ class ResponseObject(object):
# string.split(sep=None, maxsplit=1) -> list of strings
# e.g. "content.person.name" => ["content", "person.name"]
try:
top_query, sub_query = field.split('.', 1)
top_query, sub_query = field.split(".", 1)
except ValueError:
top_query = field
sub_query = None
@@ -120,7 +119,7 @@ class ResponseObject(object):
if top_query in ["status_code", "encoding", "ok", "reason", "url"]:
if sub_query:
# status_code.XX
err_msg = u"Failed to extract: {}\n".format(field)
err_msg = "Failed to extract: {}\n".format(field)
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
@@ -136,16 +135,18 @@ class ResponseObject(object):
try:
return cookies[sub_query]
except KeyError:
err_msg = u"Failed to extract cookie! => {}\n".format(field)
err_msg += u"response cookies: {}\n".format(cookies)
err_msg = "Failed to extract cookie! => {}\n".format(field)
err_msg += "response cookies: {}\n".format(cookies)
logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg)
# elapsed
elif top_query == "elapsed":
available_attributes = u"available attributes: days, seconds, microseconds, total_seconds"
available_attributes = (
"available attributes: days, seconds, microseconds, total_seconds"
)
if not sub_query:
err_msg = u"elapsed is datetime.timedelta instance, attribute should also be specified!\n"
err_msg = "elapsed is datetime.timedelta instance, attribute should also be specified!\n"
err_msg += available_attributes
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
@@ -154,7 +155,9 @@ class ResponseObject(object):
elif sub_query == "total_seconds":
return self.elapsed.total_seconds()
else:
err_msg = "{} is not valid datetime.timedelta attribute.\n".format(sub_query)
err_msg = "{} is not valid datetime.timedelta attribute.\n".format(
sub_query
)
err_msg += available_attributes
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
@@ -169,8 +172,8 @@ class ResponseObject(object):
try:
return headers[sub_query]
except KeyError:
err_msg = u"Failed to extract header! => {}\n".format(field)
err_msg += u"response headers: {}\n".format(headers)
err_msg = "Failed to extract header! => {}\n".format(field)
err_msg += "response headers: {}\n".format(headers)
logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg)
@@ -193,8 +196,12 @@ class ResponseObject(object):
return utils.query_json(body, sub_query)
else:
# content = "<html>abcdefg</html>", content.xxx
err_msg = u"Failed to extract attribute from response body! => {}\n".format(field)
err_msg += u"response body: {}\n".format(body)
err_msg = (
"Failed to extract attribute from response body! => {}\n".format(
field
)
)
err_msg += "response body: {}\n".format(body)
logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg)
@@ -214,26 +221,29 @@ class ResponseObject(object):
return utils.query_json(attributes, sub_query)
else:
# content = "attributes.new_attribute_not_exist"
err_msg = u"Failed to extract cumstom set attribute from teardown hooks! => {}\n".format(field)
err_msg += u"response set attributes: {}\n".format(attributes)
err_msg = "Failed to extract cumstom set attribute from teardown hooks! => {}\n".format(
field
)
err_msg += "response set attributes: {}\n".format(attributes)
logger.log_error(err_msg)
raise exceptions.TeardownHooksFailure(err_msg)
# others
else:
err_msg = u"Failed to extract attribute from response! => {}\n".format(field)
err_msg += u"available response attributes: status_code, cookies, elapsed, headers, content, " \
u"text, json, encoding, ok, reason, url.\n\n"
err_msg += u"If you want to set attribute in teardown_hooks, take the following example as reference:\n"
err_msg += u"response.new_attribute = 'new_attribute_value'\n"
err_msg = "Failed to extract attribute from response! => {}\n".format(field)
err_msg += (
"available response attributes: status_code, cookies, elapsed, headers, content, "
"text, json, encoding, ok, reason, url.\n\n"
)
err_msg += "If you want to set attribute in teardown_hooks, take the following example as reference:\n"
err_msg += "response.new_attribute = 'new_attribute_value'\n"
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
def extract_field(self, field):
""" extract value from requests.Response.
"""
"""extract value from requests.Response."""
if not isinstance(field, basestring):
err_msg = u"Invalid extractor! => {}\n".format(field)
err_msg = "Invalid extractor! => {}\n".format(field)
logger.log_error(err_msg)
raise exceptions.ParamsError(err_msg)
@@ -255,7 +265,7 @@ class ResponseObject(object):
return value
def extract_response(self, extractors):
""" extract value from requests.Response and store in OrderedDict.
"""extract value from requests.Response and store in OrderedDict.
Args:
extractors (list):

View File

@@ -15,7 +15,7 @@ class HookTypeEnum(Enum):
class Runner(object):
""" Running testcases.
"""Running testcases.
Examples:
>>> tests_mapping = {
@@ -52,7 +52,7 @@ class Runner(object):
"""
def __init__(self, config, http_client_session=None):
""" run testcase or testsuite.
"""run testcase or testsuite.
Args:
config (dict): testcase/testsuite config dict
@@ -87,15 +87,14 @@ class Runner(object):
self.do_hook_actions(self.testcase_teardown_hooks, HookTypeEnum.TEARDOWN)
def __clear_test_data(self):
""" clear request and response data
"""
"""clear request and response data"""
if not isinstance(self.http_client_session, HttpSession):
return
self.http_client_session.init_meta_data()
def _handle_skip_feature(self, test_dict):
""" handle skip feature for test
"""handle skip feature for test
- skip: skip current test unconditionally
- skipIf: skip current test if condition is true
- skipUnless: skip current test unless condition is true
@@ -127,7 +126,7 @@ class Runner(object):
raise SkipTest(skip_reason)
def do_hook_actions(self, actions, hook_type):
""" call hook actions.
"""call hook actions.
Args:
actions (list): each action in actions list maybe in two format.
@@ -153,9 +152,7 @@ class Runner(object):
var_name, hook_content, hook_content_eval
)
)
self.session_context.update_test_variables(
var_name, hook_content_eval
)
self.session_context.update_test_variables(var_name, hook_content_eval)
else:
# format 2
logger.log_debug("call hook function: {}".format(action))
@@ -163,7 +160,7 @@ class Runner(object):
self.session_context.eval_content(action)
def _run_test(self, test_dict):
""" run single teststep.
"""run single teststep.
Args:
test_dict (dict): teststep info
@@ -209,7 +206,7 @@ class Runner(object):
test_name = self.session_context.eval_content(test_dict.get("name", ""))
# parse test request
raw_request = test_dict.get('request', {})
raw_request = test_dict.get("request", {})
parsed_test_request = self.session_context.eval_content(raw_request)
self.session_context.update_test_variables("request", parsed_test_request)
@@ -219,12 +216,12 @@ class Runner(object):
self.do_hook_actions(setup_hooks, HookTypeEnum.SETUP)
# prepend url with base_url unless it's already an absolute URL
url = parsed_test_request.pop('url')
url = parsed_test_request.pop("url")
base_url = self.session_context.eval_content(test_dict.get("base_url", ""))
parsed_url = utils.build_url(base_url, url)
try:
method = parsed_test_request.pop('method')
method = parsed_test_request.pop("method")
parsed_test_request.setdefault("verify", self.verify)
group_name = parsed_test_request.pop("group", None)
except KeyError:
@@ -232,14 +229,12 @@ class Runner(object):
logger.log_info("{method} {url}".format(method=method, url=parsed_url))
logger.log_debug(
"request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request))
"request kwargs(raw): {kwargs}".format(kwargs=parsed_test_request)
)
# request
resp = self.http_client_session.request(
method,
parsed_url,
name=(group_name or test_name),
**parsed_test_request
method, parsed_url, name=(group_name or test_name), **parsed_test_request
)
resp_obj = response.ResponseObject(resp)
@@ -284,10 +279,7 @@ class Runner(object):
validators = test_dict.get("validate") or test_dict.get("validators") or []
validate_script = test_dict.get("validate_script", [])
if validate_script:
validators.append({
"type": "python_script",
"script": validate_script
})
validators.append({"type": "python_script", "script": validate_script})
validator = Validator(self.session_context, resp_obj)
try:
@@ -299,8 +291,7 @@ class Runner(object):
self.validation_results = validator.validation_results
def _run_testcase(self, testcase_dict):
""" run single testcase.
"""
"""run single testcase."""
self.meta_datas = []
config = testcase_dict.get("config", {})
@@ -333,7 +324,7 @@ class Runner(object):
)
def run_test(self, test_dict):
""" run single teststep of testcase.
"""run single teststep of testcase.
test_dict may be in 3 types.
Args:
@@ -370,7 +361,8 @@ class Runner(object):
# nested testcase
test_dict.setdefault("config", {}).setdefault("variables", {})
test_dict["config"]["variables"].update(
self.session_context.session_variables_mapping)
self.session_context.session_variables_mapping
)
self._run_testcase(test_dict)
else:
# api
@@ -388,8 +380,7 @@ class Runner(object):
self.meta_datas["validators"] = self.validation_results
def export_variables(self, output_variables_list):
""" export current testcase variables
"""
"""export current testcase variables"""
variables_mapping = self.session_context.session_variables_mapping
output = {}

View File

@@ -23,7 +23,7 @@ absolute_http_url_regexp = re.compile(r"^https?://", re.I)
def init_sentry_sdk():
sentry_sdk.init(
dsn="https://cc6dd86fbe9f4e7fbd95248cfcff114d@sentry.io/1862849",
release="httprunner@{}".format(__version__)
release="httprunner@{}".format(__version__),
)
with sentry_sdk.configure_scope() as scope:
@@ -32,46 +32,48 @@ def init_sentry_sdk():
class GAClient(object):
version = '1' # GA API Version
report_url = 'https://www.google-analytics.com/collect'
report_debug_url = 'https://www.google-analytics.com/debug/collect' # used for debug
version = "1" # GA API Version
report_url = "https://www.google-analytics.com/collect"
report_debug_url = (
"https://www.google-analytics.com/debug/collect" # used for debug
)
def __init__(self, tracking_id):
self.http_client = requests.Session()
self.label = str(__version__)
self.common_params = {
'v': self.version,
'tid': tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
'cid': uuid.getnode(), # Anonymous Client ID
'ua': 'HttpRunner/{}'.format(__version__),
"v": self.version,
"tid": tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
"cid": uuid.getnode(), # Anonymous Client ID
"ua": "HttpRunner/{}".format(__version__),
}
def track_event(self, category, action, value=0):
data = {
't': 'event', # Event hit type = event
'ec': category, # Required. Event Category.
'ea': action, # Required. Event Action.
'el': self.label, # Optional. Event label, used as version.
'ev': value, # Optional. Event value, must be non-negative integer
"t": "event", # Event hit type = event
"ec": category, # Required. Event Category.
"ea": action, # Required. Event Action.
"el": self.label, # Optional. Event label, used as version.
"ev": value, # Optional. Event value, must be non-negative integer
}
data.update(self.common_params)
try:
self.http_client.post(self.report_url, data=data, timeout=5)
except Exception: # ProxyError, SSLError, ConnectionError
except Exception: # ProxyError, SSLError, ConnectionError
pass
def track_user_timing(self, category, variable, duration):
data = {
't': 'timing', # Event hit type = timing
'utc': category, # Required. user timing category. e.g. jsonLoader
'utv': variable, # Required. timing variable. e.g. load
'utt': duration, # Required. time took duration.
'utl': self.label, # Optional. user timing label, used as version.
"t": "timing", # Event hit type = timing
"utc": category, # Required. user timing category. e.g. jsonLoader
"utv": variable, # Required. timing variable. e.g. load
"utt": duration, # Required. time took duration.
"utl": self.label, # Optional. user timing label, used as version.
}
data.update(self.common_params)
try:
self.http_client.post(self.report_url, data=data, timeout=5)
except Exception: # ProxyError, SSLError, ConnectionError
except Exception: # ProxyError, SSLError, ConnectionError
pass
@@ -79,23 +81,21 @@ ga_client = GAClient("UA-114587036-1")
def set_os_environ(variables_mapping):
""" set variables mapping to os.environ
"""
"""set variables mapping to os.environ"""
for variable in variables_mapping:
os.environ[variable] = variables_mapping[variable]
logger.log_debug("Set OS environment variable: {}".format(variable))
def unset_os_environ(variables_mapping):
""" set variables mapping to os.environ
"""
"""set variables mapping to os.environ"""
for variable in variables_mapping:
os.environ.pop(variable)
logger.log_debug("Unset OS environment variable: {}".format(variable))
def get_os_environ(variable_name):
""" get value of environment variable.
"""get value of environment variable.
Args:
variable_name(str): variable name
@@ -114,7 +114,7 @@ def get_os_environ(variable_name):
def build_url(base_url, path):
""" prepend url with base_url unless it's already an absolute URL """
"""prepend url with base_url unless it's already an absolute URL"""
if absolute_http_url_regexp.match(path):
return path
elif base_url:
@@ -123,8 +123,8 @@ def build_url(base_url, path):
raise ParamsError("base url missed!")
def query_json(json_content, query, delimiter='.'):
""" Do an xpath-like query with json_content.
def query_json(json_content, query, delimiter="."):
"""Do an xpath-like query with json_content.
Args:
json_content (dict/list/string): content to be queried.
@@ -158,7 +158,7 @@ def query_json(json_content, query, delimiter='.'):
"""
raise_flag = False
response_body = u"response body: {}\n".format(json_content)
response_body = "response body: {}\n".format(json_content)
try:
for key in query.split(delimiter):
if isinstance(json_content, (list, basestring)):
@@ -167,13 +167,16 @@ def query_json(json_content, query, delimiter='.'):
json_content = json_content[key]
else:
logger.log_error(
"invalid type value: {}({})".format(json_content, type(json_content)))
"invalid type value: {}({})".format(
json_content, type(json_content)
)
)
raise_flag = True
except (KeyError, ValueError, IndexError):
raise_flag = True
if raise_flag:
err_msg = u"Failed to extract! => {}\n".format(query)
err_msg = "Failed to extract! => {}\n".format(query)
err_msg += response_body
logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg)
@@ -182,7 +185,7 @@ def query_json(json_content, query, delimiter='.'):
def lower_dict_keys(origin_dict):
""" convert keys in dict to lower case
"""convert keys in dict to lower case
Args:
origin_dict (dict): mapping data structure
@@ -213,16 +216,13 @@ def lower_dict_keys(origin_dict):
if not origin_dict or not isinstance(origin_dict, dict):
return origin_dict
return {
key.lower(): value
for key, value in origin_dict.items()
}
return {key.lower(): value for key, value in origin_dict.items()}
def lower_test_dict_keys(test_dict):
""" convert keys in test_dict to lower case, convertion will occur in two places:
1, all keys in test_dict;
2, all keys in test_dict["request"]
"""convert keys in test_dict to lower case, convertion will occur in two places:
1, all keys in test_dict;
2, all keys in test_dict["request"]
"""
# convert keys in test_dict
test_dict = lower_dict_keys(test_dict)
@@ -235,7 +235,7 @@ def lower_test_dict_keys(test_dict):
def deepcopy_dict(data):
""" deepcopy dict data, ignore file object (_io.BufferedReader)
"""deepcopy dict data, ignore file object (_io.BufferedReader)
Args:
data (dict): dict data structure
@@ -271,7 +271,7 @@ def deepcopy_dict(data):
def ensure_mapping_format(variables):
""" ensure variables are in mapping format.
"""ensure variables are in mapping format.
Args:
variables (list/dict): original variables
@@ -306,7 +306,7 @@ def ensure_mapping_format(variables):
def extend_variables(raw_variables, override_variables):
""" extend raw_variables with override_variables.
"""extend raw_variables with override_variables.
override_variables will merge and override raw_variables.
Args:
@@ -343,7 +343,7 @@ def extend_variables(raw_variables, override_variables):
def get_testcase_io(testcase):
""" get and print testcase input(variables) and output(export).
"""get and print testcase input(variables) and output(export).
Args:
testcase (unittest.suite.TestSuite): corresponding to one YAML/JSON file, it has been set two attributes:
@@ -355,18 +355,14 @@ def get_testcase_io(testcase):
"""
test_runner = testcase.runner
variables = testcase.config.get("variables", {})
output_list = testcase.config.get("export") \
or testcase.config.get("output", [])
output_list = testcase.config.get("export") or testcase.config.get("output", [])
export_mapping = test_runner.export_variables(output_list)
return {
"in": variables,
"out": export_mapping
}
return {"in": variables, "out": export_mapping}
def print_info(info_mapping):
""" print info in mapping.
"""print info in mapping.
Args:
info_mapping (dict): input(variables) or output mapping.
@@ -417,10 +413,11 @@ def print_info(info_mapping):
def create_scaffold(project_name):
""" create scaffold with specified project name.
"""
"""create scaffold with specified project name."""
if os.path.isdir(project_name):
logger.log_warning(u"Folder {} exists, please specify a new folder name.".format(project_name))
logger.log_warning(
"Folder {} exists, please specify a new folder name.".format(project_name)
)
return
ga_client.track_event("Scaffold", "startproject")
@@ -433,7 +430,7 @@ def create_scaffold(project_name):
logger.color_print(msg, "BLUE")
def create_file(path, file_content=""):
with open(path, 'w') as f:
with open(path, "w") as f:
f.write(file_content)
msg = "created file: {}".format(path)
logger.color_print(msg, "BLUE")
@@ -498,24 +495,16 @@ testcases:
variables:
device_sn: $device_sn
"""
ignore_content = "\n".join([
".env",
"reports/*",
"__pycache__/*",
"*.pyc",
".python-version",
"logs/*"
])
ignore_content = "\n".join(
[".env", "reports/*", "__pycache__/*", "*.pyc", ".python-version", "logs/*"]
)
demo_debugtalk_content = """
import time
def sleep(n_secs):
time.sleep(n_secs)
"""
demo_env_content = "\n".join([
"USERNAME=leolee",
"PASSWORD=123456"
])
demo_env_content = "\n".join(["USERNAME=leolee", "PASSWORD=123456"])
create_folder(project_name)
create_folder(os.path.join(project_name, "api"))
@@ -523,15 +512,21 @@ def sleep(n_secs):
create_folder(os.path.join(project_name, "testsuites"))
create_folder(os.path.join(project_name, "reports"))
create_file(os.path.join(project_name, "api", "demo_api.yml"), demo_api_content)
create_file(os.path.join(project_name, "testcases", "demo_testcase.yml"), demo_testcase_content)
create_file(os.path.join(project_name, "testsuites", "demo_testsuite.yml"), demo_testsuite_content)
create_file(
os.path.join(project_name, "testcases", "demo_testcase.yml"),
demo_testcase_content,
)
create_file(
os.path.join(project_name, "testsuites", "demo_testsuite.yml"),
demo_testsuite_content,
)
create_file(os.path.join(project_name, "debugtalk.py"), demo_debugtalk_content)
create_file(os.path.join(project_name, ".env"), demo_env_content)
create_file(os.path.join(project_name, ".gitignore"), ignore_content)
def gen_cartesian_product(*args):
""" generate cartesian product for lists
"""generate cartesian product for lists
Args:
args (list of list): lists to be generated with cartesian product
@@ -572,11 +567,12 @@ def gen_cartesian_product(*args):
def prettify_json_file(file_list):
""" prettify JSON testcase format
"""
"""prettify JSON testcase format"""
for json_file in set(file_list):
if not json_file.endswith(".json"):
logger.log_warning("Only JSON file format can be prettified, skip: {}".format(json_file))
logger.log_warning(
"Only JSON file format can be prettified, skip: {}".format(json_file)
)
continue
logger.color_print("Start to prettify JSON file: {}".format(json_file), "GREEN")
@@ -585,22 +581,21 @@ def prettify_json_file(file_list):
file_name, file_suffix = os.path.splitext(os.path.basename(json_file))
outfile = os.path.join(dir_path, "{}.pretty.json".format(file_name))
with io.open(json_file, 'r', encoding='utf-8') as stream:
with io.open(json_file, "r", encoding="utf-8") as stream:
try:
obj = json.load(stream)
except ValueError as e:
raise SystemExit(e)
with io.open(outfile, 'w', encoding='utf-8') as out:
json.dump(obj, out, indent=4, separators=(',', ': '))
out.write('\n')
with io.open(outfile, "w", encoding="utf-8") as out:
json.dump(obj, out, indent=4, separators=(",", ": "))
out.write("\n")
print("success: {}".format(outfile))
def omit_long_data(body, omit_len=512):
""" omit too long str/bytes
"""
"""omit too long str/bytes"""
if not isinstance(body, basestring):
return body
@@ -618,8 +613,8 @@ def omit_long_data(body, omit_len=512):
def dump_json_file(json_data, json_file_abs_path):
""" dump json data to file
"""
"""dump json data to file"""
class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj):
try:
@@ -632,26 +627,28 @@ def dump_json_file(json_data, json_file_abs_path):
os.makedirs(file_foder_path)
try:
with io.open(json_file_abs_path, 'w', encoding='utf-8') as outfile:
with io.open(json_file_abs_path, "w", encoding="utf-8") as outfile:
if is_py2:
outfile.write(
unicode(json.dumps(
json_data,
indent=4,
separators=(',', ':'),
encoding="utf8",
ensure_ascii=False,
cls=PythonObjectEncoder
))
unicode(
json.dumps(
json_data,
indent=4,
separators=(",", ":"),
encoding="utf8",
ensure_ascii=False,
cls=PythonObjectEncoder,
)
)
)
else:
json.dump(
json_data,
outfile,
indent=4,
separators=(',', ':'),
separators=(",", ":"),
ensure_ascii=False,
cls=PythonObjectEncoder
cls=PythonObjectEncoder,
)
msg = "dump file: {}".format(json_file_abs_path)
@@ -663,8 +660,7 @@ def dump_json_file(json_data, json_file_abs_path):
def prepare_dump_json_file_abs_path(project_mapping, tag_name):
""" prepare dump json file absolute path.
"""
"""prepare dump json file absolute path."""
pwd_dir_path = project_mapping.get("PWD") or os.getcwd()
test_path = project_mapping.get("test_path")
@@ -676,7 +672,7 @@ def prepare_dump_json_file_abs_path(project_mapping, tag_name):
# both test_path and pwd_dir_path are absolute path
logs_dir_path = os.path.join(pwd_dir_path, "logs")
test_path_relative_path = test_path[len(pwd_dir_path)+1:]
test_path_relative_path = test_path[len(pwd_dir_path) + 1 :]
if os.path.isdir(test_path):
file_foder_path = os.path.join(logs_dir_path, test_path_relative_path)
@@ -692,7 +688,7 @@ def prepare_dump_json_file_abs_path(project_mapping, tag_name):
def dump_logs(json_data, project_mapping, tag_name):
""" dump tests data to json file.
"""dump tests data to json file.
the dumped file is located in PWD/logs folder.
Args:
@@ -711,7 +707,11 @@ def get_python2_retire_msg():
left_days = (retire_day - today).days
if left_days > 0:
retire_msg = "Python 2 will retire in {} days, why not move to Python 3?".format(left_days)
retire_msg = (
"Python 2 will retire in {} days, why not move to Python 3?".format(
left_days
)
)
else:
retire_msg = "Python 2 has been retired, you should move to Python 3."

View File

@@ -16,7 +16,7 @@ class Validator(object):
"""
def __init__(self, session_context, resp_obj):
""" initialize a Validator for each teststep (API request)
"""initialize a Validator for each teststep (API request)
Args:
session_context: HttpRunner session context
@@ -27,7 +27,7 @@ class Validator(object):
self.validation_results = {}
def __eval_validator_check(self, check_item):
""" evaluate check item in validator.
"""evaluate check item in validator.
Args:
check_item: check_item should only be the following 5 formats:
@@ -38,8 +38,9 @@ class Validator(object):
5, regex string, e.g. "LB[\d]*(.*)RB[\d]*"
"""
if isinstance(check_item, (dict, list)) \
or isinstance(check_item, parser.LazyString):
if isinstance(check_item, (dict, list)) or isinstance(
check_item, parser.LazyString
):
# format 1/2/3
check_value = self.session_context.eval_content(check_item)
else:
@@ -49,7 +50,7 @@ class Validator(object):
return check_value
def __eval_validator_expect(self, expect_item):
""" evaluate expect item in validator.
"""evaluate expect item in validator.
Args:
expect_item: expect_item should only be in 2 types:
@@ -61,12 +62,11 @@ class Validator(object):
return expect_value
def validate_script(self, script):
""" make validation with python script
"""
"""make validation with python script"""
result = {
"validate_script": "<br/>".join(script),
"check_result": "pass",
"output": ""
"output": "",
}
script = "\n ".join(script)
@@ -75,12 +75,14 @@ class Validator(object):
def run_validate_script():
{}
""".format(script)
""".format(
script
)
variables = {
"status_code": self.resp_obj.status_code,
"response_json": self.resp_obj.json,
"response": self.resp_obj
"response": self.resp_obj,
}
variables.update(self.session_context.test_variables_mapping)
variables.update(globals())
@@ -90,11 +92,13 @@ def run_validate_script():
except SyntaxError as ex:
logger.log_warning("SyntaxError in python validate script: {}".format(ex))
result["check_result"] = "fail"
result["output"] = "<br/>".join([
"ErrorMessage: {}".format(ex.msg),
"ErrorLine: {}".format(ex.lineno),
"ErrorText: {}".format(ex.text)
])
result["output"] = "<br/>".join(
[
"ErrorMessage: {}".format(ex.msg),
"ErrorLine: {}".format(ex.lineno),
"ErrorText: {}".format(ex.text),
]
)
return result
try:
@@ -117,16 +121,17 @@ def run_validate_script():
else:
line_no = "N/A"
result["output"] = "<br/>".join([
"ErrorType: {}".format(_type.__name__),
"ErrorLine: {}".format(line_no)
])
result["output"] = "<br/>".join(
[
"ErrorType: {}".format(_type.__name__),
"ErrorLine: {}".format(line_no),
]
)
return result
def validate(self, validators):
""" make validation with comparators
"""
"""make validation with comparators"""
self.validation_results = {}
if not validators:
return
@@ -154,7 +159,8 @@ def run_validate_script():
# validator should be LazyFunction object
if not isinstance(validator, parser.LazyFunction):
raise exceptions.ValidationFailure(
"validator should be parsed first: {}".format(validators))
"validator should be parsed first: {}".format(validators)
)
# evaluate validator args with context variable mapping.
validator_args = validator.get_args()
@@ -169,13 +175,10 @@ def run_validate_script():
"check": check_item,
"check_value": check_value,
"expect": expect_item,
"expect_value": expect_value
"expect_value": expect_value,
}
validate_msg = "\nvalidate: {} {} {}({})".format(
check_item,
comparator,
expect_value,
type(expect_value).__name__
check_item, comparator, expect_value, type(expect_value).__name__
)
try:
@@ -192,7 +195,7 @@ def run_validate_script():
type(check_value).__name__,
comparator,
expect_value,
type(expect_value).__name__
type(expect_value).__name__,
)
logger.log_error(validate_msg)
failures.append(validate_msg)

View File

@@ -9,6 +9,7 @@ from httprunner.builtin.functions import gen_random_string
try:
from httpbin import app as httpbin_app
HTTPBIN_HOST = "127.0.0.1"
HTTPBIN_PORT = 3458
HTTPBIN_SERVER = "http://{}:{}".format(HTTPBIN_HOST, HTTPBIN_PORT)
@@ -49,37 +50,30 @@ token_dict = {}
def get_sign(*args):
content = ''.join(args).encode('ascii')
sign_key = SECRET_KEY.encode('ascii')
content = "".join(args).encode("ascii")
sign_key = SECRET_KEY.encode("ascii")
sign = hmac.new(sign_key, content, hashlib.sha1).hexdigest()
return sign
def gen_md5(*args):
return hashlib.md5("".join(args).encode('utf-8')).hexdigest()
return hashlib.md5("".join(args).encode("utf-8")).hexdigest()
def validate_request(func):
@wraps(func)
def wrapper(*args, **kwargs):
device_sn = request.headers.get('device_sn', "")
token = request.headers.get('token', "")
device_sn = request.headers.get("device_sn", "")
token = request.headers.get("token", "")
if not device_sn or not token:
result = {
'success': False,
'msg': "device_sn or token is null."
}
result = {"success": False, "msg": "device_sn or token is null."}
response = make_response(json.dumps(result), 401)
response.headers["Content-Type"] = "application/json"
return response
if token_dict[device_sn] != token:
result = {
'success': False,
'msg': "Authorization failed!"
}
result = {"success": False, "msg": "Authorization failed!"}
response = make_response(json.dumps(result), 403)
response.headers["Content-Type"] = "application/json"
return response
@@ -89,82 +83,64 @@ def validate_request(func):
return wrapper
@app.route('/')
@app.route("/")
def index():
return "Hello World!"
@app.route('/api/get-token', methods=['POST'])
@app.route("/api/get-token", methods=["POST"])
def get_token():
device_sn = request.headers.get('device_sn', "")
os_platform = request.headers.get('os_platform', "")
app_version = request.headers.get('app_version', "")
device_sn = request.headers.get("device_sn", "")
os_platform = request.headers.get("os_platform", "")
app_version = request.headers.get("app_version", "")
data = request.get_json()
sign = data.get('sign', "")
sign = data.get("sign", "")
expected_sign = get_sign(device_sn, os_platform, app_version)
if expected_sign != sign:
result = {
'success': False,
'msg': "Authorization failed!"
}
result = {"success": False, "msg": "Authorization failed!"}
response = make_response(json.dumps(result), 403)
else:
token = gen_random_string(16)
token_dict[device_sn] = token
result = {
'success': True,
'token': token
}
result = {"success": True, "token": token}
response = make_response(json.dumps(result))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users')
@app.route("/api/users")
@validate_request
def get_users():
users_list = [user for uid, user in users_dict.items()]
users = {
'success': True,
'count': len(users_list),
'items': users_list
}
users = {"success": True, "count": len(users_list), "items": users_list}
response = make_response(json.dumps(users))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/reset-all')
@app.route("/api/reset-all")
@validate_request
def clear_users():
users_dict.clear()
result = {
'success': True
}
result = {"success": True}
response = make_response(json.dumps(result))
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>', methods=['POST'])
@app.route("/api/users/<int:uid>", methods=["POST"])
@validate_request
def create_user(uid):
user = request.get_json()
if uid not in users_dict:
result = {
'success': True,
'msg': "user created successfully."
}
result = {"success": True, "msg": "user created successfully."}
status_code = 201
users_dict[uid] = user
else:
result = {
'success': False,
'msg': "user already existed."
}
result = {"success": False, "msg": "user already existed."}
status_code = 500
response = make_response(json.dumps(result), status_code)
@@ -172,21 +148,15 @@ def create_user(uid):
return response
@app.route('/api/users/<int:uid>')
@app.route("/api/users/<int:uid>")
@validate_request
def get_user(uid):
user = users_dict.get(uid, {})
if user:
result = {
'success': True,
'data': user
}
result = {"success": True, "data": user}
status_code = 200
else:
result = {
'success': False,
'data': user
}
result = {"success": False, "data": user}
status_code = 404
response = make_response(json.dumps(result), status_code)
@@ -194,7 +164,7 @@ def get_user(uid):
return response
@app.route('/api/users/<int:uid>', methods=['PUT'])
@app.route("/api/users/<int:uid>", methods=["PUT"])
@validate_request
def update_user(uid):
user = users_dict.get(uid, {})
@@ -207,16 +177,13 @@ def update_user(uid):
success = False
status_code = 404
result = {
'success': success,
'data': user
}
result = {"success": success, "data": user}
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response
@app.route('/api/users/<int:uid>', methods=['DELETE'])
@app.route("/api/users/<int:uid>", methods=["DELETE"])
@validate_request
def delete_user(uid):
user = users_dict.pop(uid, {})
@@ -227,10 +194,7 @@ def delete_user(uid):
success = False
status_code = 404
result = {
'success': success,
'data': user
}
result = {"success": success, "data": user}
response = make_response(json.dumps(result), status_code)
response.headers["Content-Type"] = "application/json"
return response

View File

@@ -19,18 +19,13 @@ def run_httpbin():
class ApiServerUnittest(unittest.TestCase):
""" Test case class that sets up an HTTP server which can be used within the tests
"""
"""Test case class that sets up an HTTP server which can be used within the tests"""
@classmethod
def setUpClass(cls):
cls.host = "http://127.0.0.1:5000"
cls.flask_process = multiprocessing.Process(
target=run_flask
)
cls.httpbin_process = multiprocessing.Process(
target=run_httpbin
)
cls.flask_process = multiprocessing.Process(target=run_flask)
cls.httpbin_process = multiprocessing.Process(target=run_httpbin)
cls.flask_process.start()
cls.httpbin_process.start()
time.sleep(1)
@@ -44,15 +39,13 @@ class ApiServerUnittest(unittest.TestCase):
def get_token(self, user_agent, device_sn, os_platform, app_version):
url = "%s/api/get-token" % self.host
headers = {
'Content-Type': 'application/json',
'User-Agent': user_agent,
'device_sn': device_sn,
'os_platform': os_platform,
'app_version': app_version
}
data = {
'sign': get_sign(device_sn, os_platform, app_version)
"Content-Type": "application/json",
"User-Agent": user_agent,
"device_sn": device_sn,
"os_platform": os_platform,
"app_version": app_version,
}
data = {"sign": get_sign(device_sn, os_platform, app_version)}
resp = self.api_client.post(url, json=data, headers=headers)
resp_json = resp.json()
@@ -62,14 +55,11 @@ class ApiServerUnittest(unittest.TestCase):
return resp_json["token"]
def get_authenticated_headers(self):
user_agent = 'iOS/10.3'
user_agent = "iOS/10.3"
device_sn = gen_random_string(15)
os_platform = 'ios'
app_version = '2.8.6'
os_platform = "ios"
app_version = "2.8.6"
token = self.get_token(user_agent, device_sn, os_platform, app_version)
headers = {
'device_sn': device_sn,
'token': token
}
headers = {"device_sn": device_sn, "token": token}
return headers

View File

@@ -17,12 +17,7 @@ def get_base_url():
def get_default_request():
return {
"base_url": BASE_URL,
"headers": {
"content-type": "application/json"
}
}
return {"base_url": BASE_URL, "headers": {"content-type": "application/json"}}
def sum_two(m, n):
@@ -30,8 +25,8 @@ def sum_two(m, n):
def sum_status_code(status_code, expect_sum):
""" sum status code digits
e.g. 400 => 4, 201 => 3
"""sum status code digits
e.g. 400 => 4, 201 => 3
"""
sum_value = 0
for digit in str(status_code):
@@ -48,8 +43,7 @@ os.environ["TEST_ENV"] = "PRODUCTION"
def skip_test_in_production_env():
""" skip this test in production environment
"""
"""skip this test in production environment"""
return os.environ["TEST_ENV"] == "PRODUCTION"
@@ -58,16 +52,13 @@ def get_user_agent():
def gen_app_version():
return [
{"app_version": "2.8.5"},
{"app_version": "2.8.6"}
]
return [{"app_version": "2.8.5"}, {"app_version": "2.8.6"}]
def get_account():
return [
{"username": "user1", "password": "111111"},
{"username": "user2", "password": "222222"}
{"username": "user2", "password": "222222"},
]
@@ -81,7 +72,7 @@ def gen_random_string(str_len):
random_char = random.choice(string.ascii_letters + string.digits)
random_char_list.append(random_char)
random_string = ''.join(random_char_list)
random_string = "".join(random_char_list)
return random_string
@@ -94,8 +85,7 @@ def setup_hook_remove_kwargs(request):
def teardown_hook_sleep_N_secs(response, n_secs):
""" sleep n seconds after request
"""
"""sleep n seconds after request"""
if response.status_code == 200:
time.sleep(0.1)
else:
@@ -113,9 +103,11 @@ def modify_request_json(request, os_platform):
def setup_hook_httpntlmauth(request):
if "httpntlmauth" in request:
from requests_ntlm import HttpNtlmAuth
auth_account = request.pop("httpntlmauth")
request["auth"] = HttpNtlmAuth(
auth_account["username"], auth_account["password"])
auth_account["username"], auth_account["password"]
)
def alter_response(response):
@@ -123,18 +115,15 @@ def alter_response(response):
response.headers["Content-Type"] = "html/text"
response.json["headers"]["Host"] = "127.0.0.1:8888"
response.new_attribute = "new_attribute_value"
response.new_attribute_dict = {
"key": 123
}
response.new_attribute_dict = {"key": 123}
def alter_response_302(response):
response.status_code = 500
response.headers["Content-Type"] = "html/text"
response.text = "abcdef"
response.new_attribute = "new_attribute_value"
response.new_attribute_dict = {
"key": 123
}
response.new_attribute_dict = {"key": 123}
def alter_response_error(response):
@@ -143,7 +132,4 @@ def alter_response_error(response):
def gen_variables():
return {
"var_a": 1,
"var_b": 2
}
return {"var_a": 1, "var_b": 2}

View File

@@ -11,64 +11,69 @@ from tests.base import ApiServerUnittest
class TestHttpRunner(ApiServerUnittest):
def setUp(self):
self.testcase_cli_path = "tests/data/demo_testcase_cli.yml"
self.testcase_file_path_list = [
os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.yml'),
os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.json')
os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.yml"),
os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.json"),
]
testcases = [{
'config': {
'name': 'testcase description',
'request': {
'base_url': '',
'headers': {'User-Agent': 'python-requests/2.18.4'}
},
'variables': []
},
"teststeps": [
{
'name': '/api/get-token',
'request': {
'url': 'http://127.0.0.1:5000/api/get-token',
'method': 'POST',
'headers': {'Content-Type': 'application/json', 'app_version': '2.8.6',
'device_sn': 'FwgRiO7CNA50DSU', 'os_platform': 'ios', 'user_agent': 'iOS/10.3'},
'json': {'sign': '9c0c7e51c91ae963c833a4ccbab8d683c4a90c98'}
testcases = [
{
"config": {
"name": "testcase description",
"request": {
"base_url": "",
"headers": {"User-Agent": "python-requests/2.18.4"},
},
'extract': [
{'token': 'content.token'}
],
'validate': [
{'eq': ['status_code', 200]},
{'eq': ['headers.Content-Type', 'application/json']},
{'eq': ['content.success', True]}
]
"variables": [],
},
{
'name': '/api/users/1000',
'request': {
'url': 'http://127.0.0.1:5000/api/users/1000',
'method': 'POST',
'headers': {'Content-Type': 'application/json',
'device_sn': 'FwgRiO7CNA50DSU','token': '$token'},
'json': {'name': 'user1', 'password': '123456'}
"teststeps": [
{
"name": "/api/get-token",
"request": {
"url": "http://127.0.0.1:5000/api/get-token",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"app_version": "2.8.6",
"device_sn": "FwgRiO7CNA50DSU",
"os_platform": "ios",
"user_agent": "iOS/10.3",
},
"json": {
"sign": "9c0c7e51c91ae963c833a4ccbab8d683c4a90c98"
},
},
"extract": [{"token": "content.token"}],
"validate": [
{"eq": ["status_code", 200]},
{"eq": ["headers.Content-Type", "application/json"]},
{"eq": ["content.success", True]},
],
},
'validate': [
{'eq': ['status_code', 201]},
{'eq': ['headers.Content-Type', 'application/json']},
{'eq': ['content.success', True]},
{'eq': ['content.msg', 'user created successfully.']}
]
}
]
}]
self.tests_mapping = {
"testcases": testcases
}
{
"name": "/api/users/1000",
"request": {
"url": "http://127.0.0.1:5000/api/users/1000",
"method": "POST",
"headers": {
"Content-Type": "application/json",
"device_sn": "FwgRiO7CNA50DSU",
"token": "$token",
},
"json": {"name": "user1", "password": "123456"},
},
"validate": [
{"eq": ["status_code", 201]},
{"eq": ["headers.Content-Type", "application/json"]},
{"eq": ["content.success", True]},
{"eq": ["content.msg", "user created successfully."]},
],
},
],
}
]
self.tests_mapping = {"testcases": testcases}
self.runner = HttpRunner(failfast=True)
self.reset_all()
@@ -85,10 +90,7 @@ class TestHttpRunner(ApiServerUnittest):
def test_text_run_times_invalid(self):
testcases = [
{
"config": {
'name': "post data",
'variables': []
},
"config": {"name": "post data", "variables": []},
"teststeps": [
{
"name": "post data",
@@ -98,20 +100,16 @@ class TestHttpRunner(ApiServerUnittest):
"method": "POST",
"headers": {
"User-Agent": "python-requests/2.18.4",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
"data": "abc"
"data": "abc",
},
"validate": [
{"eq": ["status_code", 200]}
]
"validate": [{"eq": ["status_code", 200]}],
}
]
],
}
]
tests_mapping = {
"testcases": testcases
}
tests_mapping = {"testcases": testcases}
with self.assertRaises(exceptions.ParamsError):
self.runner.run_tests(tests_mapping)
@@ -123,12 +121,9 @@ class TestHttpRunner(ApiServerUnittest):
testcases = [
{
"config": {
'name': "post data",
'variables': {
"var1": "abc",
"var2": "def"
},
"export": ["status_code", "req_data"]
"name": "post data",
"variables": {"var1": "abc", "var2": "def"},
"export": ["status_code", "req_data"],
},
"teststeps": [
{
@@ -138,24 +133,20 @@ class TestHttpRunner(ApiServerUnittest):
"method": "POST",
"headers": {
"User-Agent": "python-requests/2.18.4",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
"data": "$var1"
"data": "$var1",
},
"extract": {
"status_code": "status_code",
"req_data": "content.data"
"req_data": "content.data",
},
"validate": [
{"eq": ["status_code", 200]}
]
"validate": [{"eq": ["status_code", 200]}],
}
]
],
}
]
tests_mapping = {
"testcases": testcases
}
tests_mapping = {"testcases": testcases}
self.runner.run_tests(tests_mapping)
vars_out = self.runner.get_vars_out()
self.assertIsInstance(vars_out, list)
@@ -166,7 +157,8 @@ class TestHttpRunner(ApiServerUnittest):
def test_save_variables_output_with_parameters(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/testsuites/create_users_with_parameters.yml')
os.getcwd(), "tests/testsuites/create_users_with_parameters.yml"
)
self.runner.run(testcase_file_path)
vars_out = self.runner.get_vars_out()
self.assertIsInstance(vars_out, list)
@@ -188,7 +180,7 @@ class TestHttpRunner(ApiServerUnittest):
self.assertEqual(summary["stat"]["teststeps"]["total"], 10)
self.assertEqual(summary["stat"]["teststeps"]["skipped"], 4)
report_save_dir = os.path.join(os.getcwd(), 'reports', "demo")
report_save_dir = os.path.join(os.getcwd(), "reports", "demo")
report.gen_html_report(summary, report_dir=report_save_dir)
self.assertGreater(len(os.listdir(report_save_dir)), 0)
shutil.rmtree(report_save_dir)
@@ -200,7 +192,7 @@ class TestHttpRunner(ApiServerUnittest):
self.assertEqual(summary["stat"]["teststeps"]["total"], 10)
self.assertEqual(summary["stat"]["teststeps"]["skipped"], 4)
report_file = os.path.join(os.getcwd(), 'reports', "demo", "test.html")
report_file = os.path.join(os.getcwd(), "reports", "demo", "test.html")
report.gen_html_report(summary, report_file=report_file)
report_save_dir = os.path.dirname(report_file)
self.assertEqual(len(os.listdir(report_save_dir)), 1)
@@ -208,7 +200,7 @@ class TestHttpRunner(ApiServerUnittest):
shutil.rmtree(report_save_dir)
def test_log_file(self):
log_file_path = os.path.join(os.getcwd(), 'reports', "test_log_file.log")
log_file_path = os.path.join(os.getcwd(), "reports", "test_log_file.log")
runner = HttpRunner(failfast=True, log_file=log_file_path)
runner.run(self.testcase_cli_path)
self.assertTrue(os.path.isfile(log_file_path))
@@ -223,10 +215,7 @@ class TestHttpRunner(ApiServerUnittest):
self.assertIn("records", summary["details"][0])
def test_run_yaml_upload(self):
upload_cases_list = [
"tests/httpbin/upload.yml",
"tests/httpbin/upload.v2.yml"
]
upload_cases_list = ["tests/httpbin/upload.yml", "tests/httpbin/upload.v2.yml"]
for upload_case in upload_cases_list:
summary = self.runner.run(upload_case)
self.assertTrue(summary["success"])
@@ -238,10 +227,7 @@ class TestHttpRunner(ApiServerUnittest):
def test_run_post_data(self):
testcases = [
{
"config": {
'name': "post data",
'variables': []
},
"config": {"name": "post data", "variables": []},
"teststeps": [
{
"name": "post data",
@@ -250,35 +236,32 @@ class TestHttpRunner(ApiServerUnittest):
"method": "POST",
"headers": {
"User-Agent": "python-requests/2.18.4",
"Content-Type": "application/json"
"Content-Type": "application/json",
},
"data": "abc"
"data": "abc",
},
"validate": [
{"eq": ["status_code", 200]}
]
"validate": [{"eq": ["status_code", 200]}],
}
]
],
}
]
tests_mapping = {
"testcases": testcases
}
tests_mapping = {"testcases": testcases}
summary = self.runner.run_tests(tests_mapping)
self.assertTrue(summary["success"])
self.assertEqual(summary["stat"]["testcases"]["total"], 1)
self.assertEqual(summary["stat"]["teststeps"]["total"], 1)
resp_json = json.loads(summary["details"][0]["records"][0]["meta_datas"]["data"][0]["response"]["body"])
self.assertEqual(
resp_json["data"],
"abc"
resp_json = json.loads(
summary["details"][0]["records"][0]["meta_datas"]["data"][0]["response"][
"body"
]
)
self.assertEqual(resp_json["data"], "abc")
def test_html_report_repsonse_image(self):
runner = HttpRunner(failfast=True)
summary = runner.run("tests/httpbin/load_image.yml")
report_save_dir = os.path.join(os.getcwd(), 'reports', "demo")
report_save_dir = os.path.join(os.getcwd(), "reports", "demo")
report_path = report.gen_html_report(summary, report_dir=report_save_dir)
self.assertTrue(os.path.isfile(report_path))
shutil.rmtree(report_save_dir)
@@ -286,7 +269,9 @@ class TestHttpRunner(ApiServerUnittest):
def test_testcase_layer_with_api(self):
summary = self.runner.run("tests/testcases/setup.yml")
self.assertTrue(summary["success"])
self.assertEqual(summary["details"][0]["records"][0]["name"], "get token (setup)")
self.assertEqual(
summary["details"][0]["records"][0]["name"], "get token (setup)"
)
self.assertEqual(summary["stat"]["testcases"]["total"], 1)
self.assertEqual(summary["stat"]["teststeps"]["total"], 2)
@@ -301,8 +286,7 @@ class TestHttpRunner(ApiServerUnittest):
self.assertFalse(summary["success"])
def test_run_httprunner_with_hooks(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/httpbin/hooks.yml')
testcase_file_path = os.path.join(os.getcwd(), "tests/httpbin/hooks.yml")
start_time = time.time()
summary = self.runner.run(testcase_file_path)
end_time = time.time()
@@ -319,11 +303,9 @@ class TestHttpRunner(ApiServerUnittest):
"request": {
"url": "{}/headers".format(HTTPBIN_SERVER),
"method": "GET",
"data": "abc"
"data": "abc",
},
"teardown_hooks": [
"${alter_response($response)}"
],
"teardown_hooks": ["${alter_response($response)}"],
"validate": [
{"eq": ["status_code", 500]},
{"eq": ["headers.content-type", "html/text"]},
@@ -332,16 +314,16 @@ class TestHttpRunner(ApiServerUnittest):
{"eq": ["text.headers.Host", "127.0.0.1:8888"]},
{"eq": ["new_attribute", "new_attribute_value"]},
{"eq": ["new_attribute_dict", {"key": 123}]},
{"eq": ["new_attribute_dict.key", 123]}
]
{"eq": ["new_attribute_dict.key", 123]},
],
}
]
],
}
]
tests_mapping = {
"project_mapping": loader.load_project_data("tests"),
"testcases": testcases
"testcases": testcases,
}
summary = self.runner.run_tests(tests_mapping)
self.assertTrue(summary["success"])
@@ -349,30 +331,24 @@ class TestHttpRunner(ApiServerUnittest):
def test_run_httprunner_with_teardown_hooks_not_exist_attribute(self):
testcases = [
{
"config": {
"name": "test teardown hooks"
},
"config": {"name": "test teardown hooks"},
"teststeps": [
{
"name": "test teardown hooks",
"request": {
"url": "{}/headers".format(HTTPBIN_SERVER),
"method": "GET",
"data": "abc"
"data": "abc",
},
"teardown_hooks": [
"${alter_response($response)}"
],
"validate": [
{"eq": ["attribute_not_exist", "new_attribute"]}
]
"teardown_hooks": ["${alter_response($response)}"],
"validate": [{"eq": ["attribute_not_exist", "new_attribute"]}],
}
]
],
}
]
tests_mapping = {
"project_mapping": loader.load_project_data("tests"),
"testcases": testcases
"testcases": testcases,
}
summary = self.runner.run_tests(tests_mapping)
self.assertFalse(summary["success"])
@@ -381,27 +357,23 @@ class TestHttpRunner(ApiServerUnittest):
def test_run_httprunner_with_teardown_hooks_error(self):
testcases = [
{
"config": {
"name": "test teardown hooks"
},
"config": {"name": "test teardown hooks"},
"teststeps": [
{
"name": "test teardown hooks",
"request": {
"url": "{}/headers".format(HTTPBIN_SERVER),
"method": "GET",
"data": "abc"
"data": "abc",
},
"teardown_hooks": [
"${alter_response_error($response)}"
]
"teardown_hooks": ["${alter_response_error($response)}"],
}
]
],
}
]
tests_mapping = {
"project_mapping": loader.load_project_data("tests"),
"testcases": testcases
"testcases": testcases,
}
summary = self.runner.run_tests(tests_mapping)
self.assertFalse(summary["success"])
@@ -478,29 +450,32 @@ class TestHttpRunner(ApiServerUnittest):
self.assertEqual(summary["stat"]["teststeps"]["total"], 3)
self.assertEqual(summary["stat"]["teststeps"]["successes"], 3)
def test_run_testcase_template_variables(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_variables.yml')
os.getcwd(), "tests/data/demo_testcase_variables.yml"
)
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
def test_run_testcase_template_import_functions(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_functions.yml')
os.getcwd(), "tests/data/demo_testcase_functions.yml"
)
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
def test_run_testcase_layered(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
os.getcwd(), "tests/data/demo_testcase_layer.yml"
)
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
self.assertEqual(len(summary["details"]), 1)
def test_run_testcase_output(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
os.getcwd(), "tests/data/demo_testcase_layer.yml"
)
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
self.assertIn("token", summary["details"][0]["in_out"]["out"])
@@ -509,10 +484,9 @@ class TestHttpRunner(ApiServerUnittest):
def test_run_testcase_with_variables_mapping(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
variables_mapping = {
"app_version": '2.9.7'
}
os.getcwd(), "tests/data/demo_testcase_layer.yml"
)
variables_mapping = {"app_version": "2.9.7"}
summary = self.runner.run(testcase_file_path, mapping=variables_mapping)
self.assertTrue(summary["success"])
self.assertIn("token", summary["details"][0]["in_out"]["out"])
@@ -521,7 +495,8 @@ class TestHttpRunner(ApiServerUnittest):
def test_run_testcase_with_parameters(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/testsuites/create_users_with_parameters.yml')
os.getcwd(), "tests/testsuites/create_users_with_parameters.yml"
)
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
self.assertEqual(len(summary["details"]), 3 * 2)
@@ -530,16 +505,13 @@ class TestHttpRunner(ApiServerUnittest):
self.assertEqual(summary["stat"]["teststeps"]["total"], 3 * 2 * 2)
self.assertEqual(
summary["details"][0]["name"],
"create user 101 and check result for TESTSUITE_X1."
"create user 101 and check result for TESTSUITE_X1.",
)
self.assertEqual(
summary["details"][5]["name"],
"create user 103 and check result for TESTSUITE_X2."
)
self.assertEqual(
summary["details"][0]["stat"]["total"],
2
"create user 103 and check result for TESTSUITE_X2.",
)
self.assertEqual(summary["details"][0]["stat"]["total"], 2)
records_name_list = [
summary["details"][i]["records"][1]["meta_datas"][1]["name"]
for i in range(6)
@@ -552,59 +524,51 @@ class TestHttpRunner(ApiServerUnittest):
"create user 102 for TESTSUITE_X1",
"create user 102 for TESTSUITE_X2",
"create user 103 for TESTSUITE_X1",
"create user 103 for TESTSUITE_X2"
}
"create user 103 for TESTSUITE_X2",
},
)
def test_validate_response_content(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/httpbin/basic.yml')
testcase_file_path = os.path.join(os.getcwd(), "tests/httpbin/basic.yml")
summary = self.runner.run(testcase_file_path)
self.assertTrue(summary["success"])
def test_html_report_xss(self):
testcases = [
{
"config": {
'name': "post data"
},
"config": {"name": "post data"},
"teststeps": [
{
"name": "post data",
"request": {
"url": "{}/anything".format(HTTPBIN_SERVER),
"method": "POST",
"headers": {
"Content-Type": "application/json"
},
"headers": {"Content-Type": "application/json"},
"json": {
'success': False,
"person": "<img src=x onerror=alert(1)>"
}
"success": False,
"person": "<img src=x onerror=alert(1)>",
},
},
"validate": [
{"eq": ["status_code", 200]}
]
"validate": [{"eq": ["status_code", 200]}],
}
]
],
}
]
tests_mapping = {
"testcases": testcases
}
tests_mapping = {"testcases": testcases}
summary = self.runner.run(tests_mapping)
report_path = report.gen_html_report(summary)
with open(report_path) as f:
content = f.read()
m = re.findall(
re.escape("&#34;person&#34;: &#34;&lt;img src=x onerror=alert(1)&gt;&#34;"),
content
re.escape(
"&#34;person&#34;: &#34;&lt;img src=x onerror=alert(1)&gt;&#34;"
),
content,
)
self.assertEqual(len(m), 2)
class TestApi(ApiServerUnittest):
def test_testcase_loader(self):
testcase_path = "tests/testcases/setup.yml"
tests_mapping = loader.load_cases(testcase_path)
@@ -628,7 +592,9 @@ class TestApi(ApiServerUnittest):
self.assertEqual(testcase_tests[0]["name"], "get token (setup)")
self.assertIsInstance(testcase_tests[0]["variables"], dict)
self.assertIn("api_def", testcase_tests[0])
self.assertEqual(testcase_tests[0]["api_def"]["request"]["url"], "/api/get-token")
self.assertEqual(
testcase_tests[0]["api_def"]["request"]["url"], "/api/get-token"
)
def test_testcase_parser(self):
testcase_path = "tests/testcases/setup.yml"
@@ -696,7 +662,7 @@ class TestApi(ApiServerUnittest):
"tests/testcases/create_user.yml",
"tests/testcases/create_user.v2.yml",
"tests/testcases/create_user.json",
"tests/testcases/create_user.v2.json"
"tests/testcases/create_user.v2.json",
]:
tests_mapping = loader.load_cases(testcase_path)
testcases = parser.parse_tests(tests_mapping)
@@ -708,11 +674,10 @@ class TestApi(ApiServerUnittest):
results = tests_results[0][1]
self.assertEqual(
results.records[0]["name"],
"setup and reset all (override) for TESTCASE_CREATE_XXX."
"setup and reset all (override) for TESTCASE_CREATE_XXX.",
)
self.assertEqual(
results.records[1]["name"],
"create user and check result."
results.records[1]["name"], "create user and check result."
)
def test_testsuite_loader(self):
@@ -740,11 +705,14 @@ class TestApi(ApiServerUnittest):
self.assertIn("testcase_def", testcase_tests)
self.assertEqual(testcase_tests["name"], "create user 1000 and check result.")
self.assertIsInstance(testcase_tests["testcase_def"], dict)
self.assertEqual(testcase_tests["testcase_def"]["config"]["name"], "create user and check result.")
self.assertEqual(
testcase_tests["testcase_def"]["config"]["name"],
"create user and check result.",
)
self.assertEqual(len(testcase_tests["testcase_def"]["teststeps"]), 2)
self.assertEqual(
testcase_tests["testcase_def"]["teststeps"][0]["name"],
"setup and reset all (override) for $device_sn."
"setup and reset all (override) for $device_sn.",
)
def test_testsuite_parser(self):
@@ -756,14 +724,13 @@ class TestApi(ApiServerUnittest):
self.assertEqual(len(parsed_testcases[0]["teststeps"]), 2)
testcase1 = parsed_testcases[0]["teststeps"][0]
self.assertIn("setup and reset all (override)", testcase1["config"]["name"].raw_string)
self.assertIn(
"setup and reset all (override)", testcase1["config"]["name"].raw_string
)
teststeps = testcase1["teststeps"]
self.assertNotIn("testcase_def", testcase1)
self.assertEqual(len(teststeps), 2)
self.assertEqual(
teststeps[0]["request"]["url"],
"/api/get-token"
)
self.assertEqual(teststeps[0]["request"]["url"], "/api/get-token")
def test_testsuite_add_tests(self):
testcase_path = "tests/testsuites/create_users.yml"
@@ -775,7 +742,9 @@ class TestApi(ApiServerUnittest):
self.assertEqual(len(test_suite._tests), 2)
tests = test_suite._tests[0].teststeps
self.assertIn("setup and reset all (override)", tests[0]["config"]["name"].raw_string)
self.assertIn(
"setup and reset all (override)", tests[0]["config"]["name"].raw_string
)
def test_testsuite_run_suite(self):
testcase_path = "tests/testsuites/create_users.yml"
@@ -790,11 +759,5 @@ class TestApi(ApiServerUnittest):
self.assertEqual(len(tests_results[0][1].records), 2)
results = tests_results[0][1]
self.assertIn(
"setup and reset all (override)",
results.records[0]["name"]
)
self.assertEqual(
results.records[1]["name"],
"create user and check result."
)
self.assertIn("setup and reset all (override)", results.records[0]["name"])
self.assertEqual(results.records[1]["name"], "create user and check result.")

View File

@@ -2,7 +2,6 @@ from tests.base import ApiServerUnittest
class TestApiServer(ApiServerUnittest):
def setUp(self):
super(TestApiServer, self).setUp()
self.headers = self.get_authenticated_headers()
@@ -25,10 +24,7 @@ class TestApiServer(ApiServerUnittest):
def create_user(self, uid, name, password):
url = "%s/api/users/%d" % (self.host, uid)
data = {
'name': name,
'password': password
}
data = {"name": name, "password": password}
return self.api_client.post(url, headers=self.headers, json=data)
def get_user(self, uid):
@@ -37,10 +33,7 @@ class TestApiServer(ApiServerUnittest):
def update_user(self, uid, name, password):
url = "%s/api/users/%d" % (self.host, uid)
data = {
'name': name,
'password': password
}
data = {"name": name, "password": password}
return self.api_client.put(url, headers=self.headers, json=data)
def delete_user(self, uid):
@@ -50,63 +43,63 @@ class TestApiServer(ApiServerUnittest):
def test_reset_all(self):
resp = self.reset_all()
self.assertEqual(200, resp.status_code)
self.assertEqual(True, resp.json()['success'])
self.assertEqual(True, resp.json()["success"])
def test_create_user_not_existed(self):
resp = self.create_user(1000, 'user1', '123456')
resp = self.create_user(1000, "user1", "123456")
self.assertEqual(201, resp.status_code)
self.assertEqual(True, resp.json()['success'])
self.assertEqual(True, resp.json()["success"])
def test_create_user_existed(self):
resp = self.create_user(1000, 'user1', '123456')
resp = self.create_user(1000, 'user1', '123456')
resp = self.create_user(1000, "user1", "123456")
resp = self.create_user(1000, "user1", "123456")
self.assertEqual(500, resp.status_code)
def test_get_users_empty(self):
resp = self.get_users()
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['count'], 0)
self.assertEqual(resp.json()["count"], 0)
def test_get_users_not_empty(self):
resp = self.create_user(1000, 'user1', '123456')
resp = self.create_user(1000, "user1", "123456")
resp = self.get_users()
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['count'], 1)
self.assertEqual(resp.json()["count"], 1)
resp = self.create_user(1001, 'user2', '123456')
resp = self.create_user(1001, "user2", "123456")
resp = self.get_users()
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['count'], 2)
self.assertEqual(resp.json()["count"], 2)
def test_get_user_not_existed(self):
resp = self.get_user(1000)
self.assertEqual(404, resp.status_code)
self.assertEqual(resp.json()['success'], False)
self.assertEqual(resp.json()["success"], False)
def test_get_user_existed(self):
self.create_user(1000, 'user1', '123456')
self.create_user(1000, "user1", "123456")
resp = self.get_user(1000)
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['success'], True)
self.assertEqual(resp.json()["success"], True)
def test_update_user_not_existed(self):
resp = self.update_user(1000, 'user1', '123456')
resp = self.update_user(1000, "user1", "123456")
self.assertEqual(404, resp.status_code)
self.assertEqual(resp.json()['success'], False)
self.assertEqual(resp.json()["success"], False)
def test_update_user_existed(self):
self.create_user(1000, 'user1', '123456')
resp = self.update_user(1000, 'user2', '123456')
self.create_user(1000, "user1", "123456")
resp = self.update_user(1000, "user2", "123456")
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['data']['name'], 'user2')
self.assertEqual(resp.json()["data"]["name"], "user2")
def test_delete_user_not_existed(self):
resp = self.delete_user(1000)
self.assertEqual(404, resp.status_code)
self.assertEqual(resp.json()['success'], False)
self.assertEqual(resp.json()["success"], False)
def test_delete_user_existed(self):
self.create_user(1000, 'leo', '123456')
self.create_user(1000, "leo", "123456")
resp = self.delete_user(1000)
self.assertEqual(200, resp.status_code)
self.assertEqual(resp.json()['success'], True)
self.assertEqual(resp.json()["success"], True)

View File

@@ -6,7 +6,6 @@ from httprunner.compat import io
class TestCli(unittest.TestCase):
def setUp(self):
self.captured_output = io.StringIO()
sys.stdout = self.captured_output
@@ -23,6 +22,7 @@ class TestCli(unittest.TestCase):
self.assertEqual(cm.exception.code, 0)
from httprunner import __version__
self.assertIn(__version__, self.captured_output.getvalue().strip())
def test_show_help(self):
@@ -34,4 +34,5 @@ class TestCli(unittest.TestCase):
self.assertEqual(cm.exception.code, 0)
from httprunner import __description__
self.assertIn(__description__, self.captured_output.getvalue().strip())

View File

@@ -20,30 +20,21 @@ class TestHttpClient(ApiServerUnittest):
def test_request_with_full_url(self):
url = "%s/api/users/1000" % self.host
data = {
'name': 'user1',
'password': '123456'
}
data = {"name": "user1", "password": "123456"}
resp = self.api_client.post(url, json=data, headers=self.headers)
self.assertEqual(201, resp.status_code)
self.assertEqual(True, resp.json()['success'])
self.assertEqual(True, resp.json()["success"])
def test_request_without_base_url(self):
url = "{}/api/users/1000".format(self.host)
data = {
'name': 'user1',
'password': '123456'
}
data = {"name": "user1", "password": "123456"}
resp = self.api_client.post(url, json=data, headers=self.headers)
self.assertEqual(201, resp.status_code)
self.assertEqual(True, resp.json()['success'])
self.assertEqual(True, resp.json()["success"])
def test_request_post_data(self):
url = "{}/api/users/1000".format(self.host)
data = {
'name': 'user1',
'password': '123456'
}
data = {"name": "user1", "password": "123456"}
resp = self.api_client.post(url, json=data, headers=self.headers)
# b'{"name": "user1", "password": "123456"}'
self.assertIn(b'"name": "user1"', resp.request.body)
@@ -56,14 +47,8 @@ class TestHttpClient(ApiServerUnittest):
def test_request_with_cookies(self):
url = "{}/api/users/1000".format(self.host)
data = {
'name': 'user1',
'password': '123456'
}
cookies = {
"a": "1",
"b": "2"
}
data = {"name": "user1", "password": "123456"}
cookies = {"a": "1", "b": "2"}
resp = self.api_client.get(url, cookies=cookies, headers=self.headers)
self.assertEqual(resp.request._cookies["a"], "1")
self.assertEqual(resp.request._cookies["b"], "2")

View File

@@ -6,17 +6,13 @@ from tests.base import ApiServerUnittest, gen_random_string
class TestContext(ApiServerUnittest):
def setUp(self):
loader.load_project_data(os.path.join(os.getcwd(), "tests"))
self.context = context.SessionContext(
variables={"SECRET_KEY": "DebugTalk"}
)
self.context = context.SessionContext(variables={"SECRET_KEY": "DebugTalk"})
def test_init_test_variables_initialize(self):
self.assertEqual(
self.context.test_variables_mapping,
{'SECRET_KEY': 'DebugTalk'}
self.context.test_variables_mapping, {"SECRET_KEY": "DebugTalk"}
)
def test_init_test_variables(self):
@@ -28,36 +24,25 @@ class TestContext(ApiServerUnittest):
# "data": '{"name": "$username", "password": "123456"}',
"TOKEN": "debugtalk",
"username": "user1",
"num": 6
}
functions = {
"gen_random_string": gen_random_string,
"gen_md5": gen_md5
"num": 6,
}
functions = {"gen_random_string": gen_random_string, "gen_md5": gen_md5}
variables = parser.prepare_lazy_data(variables, functions, variables.keys())
variables = parser.parse_variables_mapping(variables)
self.context.init_test_variables(variables)
variables_mapping = self.context.test_variables_mapping
self.assertEqual(len(variables_mapping["random"]), 6)
self.assertEqual(len(variables_mapping["authorization"]), 32)
self.assertEqual(variables_mapping["data"], 'user1')
self.assertEqual(variables_mapping["data"], "user1")
def test_update_seesion_variables(self):
self.context.update_session_variables({"TOKEN": "debugtalk"})
self.assertEqual(
self.context.session_variables_mapping["TOKEN"],
"debugtalk"
)
self.assertEqual(self.context.session_variables_mapping["TOKEN"], "debugtalk")
def test_eval_content_variables(self):
variables = {
"SECRET_KEY": "DebugTalk"
}
variables = {"SECRET_KEY": "DebugTalk"}
content = parser.prepare_lazy_data("abc$SECRET_KEY", {}, variables.keys())
self.assertEqual(
self.context.eval_content(content),
"abcDebugTalk"
)
self.assertEqual(self.context.eval_content(content), "abcDebugTalk")
# TODO: fix variable extraction
# content = "abc$SECRET_KEYdef"
@@ -71,12 +56,9 @@ class TestContext(ApiServerUnittest):
"random": "${gen_random_string(5)}",
"data": '{"name": "user", "password": "123456"}',
"authorization": "${gen_md5($TOKEN, $data, $random)}",
"TOKEN": "debugtalk"
}
functions = {
"gen_random_string": gen_random_string,
"gen_md5": gen_md5
"TOKEN": "debugtalk",
}
functions = {"gen_random_string": gen_random_string, "gen_md5": gen_md5}
variables = parser.prepare_lazy_data(variables, functions, variables.keys())
variables = parser.parse_variables_mapping(variables)
self.context.init_test_variables(variables)
@@ -88,14 +70,12 @@ class TestContext(ApiServerUnittest):
"Content-Type": "application/json",
"authorization": "$authorization",
"random": "$random",
"secret_key": "$SECRET_KEY"
"secret_key": "$SECRET_KEY",
},
"data": "$data"
"data": "$data",
}
prepared_request = parser.prepare_lazy_data(
request,
functions,
{"authorization", "random", "SECRET_KEY", "data"}
request, functions, {"authorization", "random", "SECRET_KEY", "data"}
)
parsed_request = self.context.eval_content(prepared_request)
self.assertIn("authorization", parsed_request["headers"])
@@ -104,17 +84,14 @@ class TestContext(ApiServerUnittest):
self.assertEqual(len(parsed_request["headers"]["random"]), 5)
self.assertIn("data", parsed_request)
self.assertEqual(
parsed_request["data"],
'{"name": "user", "password": "123456"}'
parsed_request["data"], '{"name": "user", "password": "123456"}'
)
self.assertEqual(parsed_request["headers"]["secret_key"], "DebugTalk")
def test_validate(self):
testcases = [
{
"config": {
'name': "test validation"
},
"config": {"name": "test validation"},
"teststeps": [
{
"name": "test validation",
@@ -124,26 +101,32 @@ class TestContext(ApiServerUnittest):
},
"variables": {
"resp_status_code": 200,
"resp_body_success": True
"resp_body_success": True,
},
"validate": [
{"eq": ["$resp_status_code", 200]},
{"check": "$resp_status_code", "comparator": "eq", "expect": 200},
{
"check": "$resp_status_code",
"comparator": "eq",
"expect": 200,
},
{"check": "$resp_body_success", "expect": True},
{"check": "${is_status_code_200($resp_status_code)}", "expect": True}
]
{
"check": "${is_status_code_200($resp_status_code)}",
"expect": True,
},
],
}
]
],
}
]
from tests.debugtalk import is_status_code_200
tests_mapping = {
"project_mapping": {
"functions": {
"is_status_code_200": is_status_code_200
}
"functions": {"is_status_code_200": is_status_code_200}
},
"testcases": testcases
"testcases": testcases,
}
testcases = parser.parse_tests(tests_mapping)
parsed_testcase = testcases[0]
@@ -154,9 +137,7 @@ class TestContext(ApiServerUnittest):
def test_validate_exception(self):
testcases = [
{
"config": {
'name': "test validation"
},
"config": {"name": "test validation"},
"teststeps": [
{
"name": "test validation",
@@ -166,20 +147,22 @@ class TestContext(ApiServerUnittest):
},
"variables": {
"resp_status_code": 200,
"resp_body_success": True
"resp_body_success": True,
},
"validate": [
{"eq": ["$resp_status_code", 201]},
{"check": "$resp_status_code", "expect": 201},
{"check": "$resp_body_success", "comparator": "eq", "expect": True}
]
{
"check": "$resp_body_success",
"comparator": "eq",
"expect": True,
},
],
}
]
],
}
]
tests_mapping = {
"testcases": testcases
}
tests_mapping = {"testcases": testcases}
testcases = parser.parse_tests(tests_mapping)
parsed_testcase = testcases[0]
test_runner = runner.Runner(parsed_testcase["config"])

View File

@@ -5,15 +5,13 @@ from httprunner.ext.locusts.utils import prepare_locust_tests
class TestLocust(unittest.TestCase):
def test_prepare_locust_tests(self):
path = os.path.join(
os.getcwd(), 'tests/locust_tests/demo_locusts.yml')
path = os.path.join(os.getcwd(), "tests/locust_tests/demo_locusts.yml")
locust_tests = prepare_locust_tests(path)
self.assertEqual(len(locust_tests), 2 + 3)
name_list = [
"create user 1000 and check result.",
"create user 1001 and check result."
"create user 1001 and check result.",
]
self.assertIn(locust_tests[0]["config"]["name"], name_list)
self.assertIn(locust_tests[4]["config"]["name"], name_list)

View File

@@ -1,4 +1,3 @@
import os
import unittest
@@ -7,14 +6,15 @@ from httprunner.loader import buildup
class TestModuleLoader(unittest.TestCase):
def test_filter_module_functions(self):
module_functions = buildup.load_module_functions(buildup)
self.assertIn("load_module_functions", module_functions)
self.assertNotIn("is_py3", module_functions)
def test_load_debugtalk_module(self):
project_mapping = buildup.load_project_data(os.path.join(os.getcwd(), "httprunner"))
project_mapping = buildup.load_project_data(
os.path.join(os.getcwd(), "httprunner")
)
self.assertNotIn("alter_response", project_mapping["functions"])
project_mapping = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
@@ -28,51 +28,38 @@ class TestModuleLoader(unittest.TestCase):
project_mapping = buildup.load_project_data("tests/data/demo_testcase.yml")
project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"]
self.assertEqual(
project_working_directory,
os.path.join(os.getcwd(), "tests")
)
self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
self.assertIn("gen_md5", debugtalk_functions)
project_mapping = buildup.load_project_data("tests/base.py")
project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"]
self.assertEqual(
project_working_directory,
os.path.join(os.getcwd(), "tests")
)
self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
self.assertIn("gen_md5", debugtalk_functions)
project_mapping = buildup.load_project_data("httprunner/__init__.py")
project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"]
self.assertEqual(
project_working_directory,
os.getcwd()
)
self.assertEqual(project_working_directory, os.getcwd())
self.assertEqual(debugtalk_functions, {})
class TestSuiteLoader(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.project_mapping = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
cls.project_mapping = buildup.load_project_data(
os.path.join(os.getcwd(), "tests")
)
cls.tests_def_mapping = buildup.tests_def_mapping
def test_load_teststep_api(self):
raw_test = {
"name": "create user (override).",
"api": "api/create_user.yml",
"variables": [
{"uid": "999"}
]
"variables": [{"uid": "999"}],
}
teststep = buildup.load_teststep(raw_test)
self.assertEqual(
"create user (override).",
teststep["name"]
)
self.assertEqual("create user (override).", teststep["name"])
self.assertIn("api_def", teststep)
api_def = teststep["api_def"]
self.assertEqual(api_def["name"], "create user")
@@ -82,15 +69,10 @@ class TestSuiteLoader(unittest.TestCase):
raw_test = {
"name": "setup and reset all (override).",
"testcase": "testcases/setup.yml",
"variables": [
{"device_sn": "$device_sn"}
]
"variables": [{"device_sn": "$device_sn"}],
}
testcase = buildup.load_teststep(raw_test)
self.assertEqual(
"setup and reset all (override).",
testcase["name"]
)
self.assertEqual("setup and reset all (override).", testcase["name"])
tests = testcase["testcase_def"]["teststeps"]
self.assertEqual(len(tests), 2)
self.assertEqual(tests[0]["name"], "get token (setup)")
@@ -106,7 +88,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testcase(self):
for loaded_content in [
buildup.load_test_file("tests/testcases/setup.yml"),
buildup.load_test_file("tests/testcases/setup.json")
buildup.load_test_file("tests/testcases/setup.json"),
]:
self.assertEqual(loaded_content["type"], "testcase")
self.assertIn("path", loaded_content)
@@ -118,7 +100,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testcase_v2(self):
for loaded_content in [
buildup.load_test_file("tests/testcases/setup.v2.yml"),
buildup.load_test_file("tests/testcases/setup.v2.json")
buildup.load_test_file("tests/testcases/setup.v2.json"),
]:
self.assertEqual(loaded_content["type"], "testcase")
self.assertIn("path", loaded_content)
@@ -130,38 +112,45 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testsuite(self):
for loaded_content in [
buildup.load_test_file("tests/testsuites/create_users.yml"),
buildup.load_test_file("tests/testsuites/create_users.json")
buildup.load_test_file("tests/testsuites/create_users.json"),
]:
self.assertEqual(loaded_content["type"], "testsuite")
testcases = loaded_content["testcases"]
self.assertEqual(len(testcases), 2)
self.assertIn('create user 1000 and check result.', testcases)
self.assertIn('testcase_def', testcases["create user 1000 and check result."])
self.assertIn("create user 1000 and check result.", testcases)
self.assertIn(
"testcase_def", testcases["create user 1000 and check result."]
)
self.assertEqual(
testcases["create user 1000 and check result."]["testcase_def"]["config"]["name"],
"create user and check result."
testcases["create user 1000 and check result."]["testcase_def"][
"config"
]["name"],
"create user and check result.",
)
def test_load_test_file_testsuite_v2(self):
for loaded_content in [
buildup.load_test_file("tests/testsuites/create_users.v2.yml"),
buildup.load_test_file("tests/testsuites/create_users.v2.json")
buildup.load_test_file("tests/testsuites/create_users.v2.json"),
]:
self.assertEqual(loaded_content["type"], "testsuite")
testcases = loaded_content["testcases"]
self.assertEqual(len(testcases), 2)
self.assertIn('create user 1000 and check result.', testcases)
self.assertIn('testcase_def', testcases["create user 1000 and check result."])
self.assertIn("create user 1000 and check result.", testcases)
self.assertIn(
"testcase_def", testcases["create user 1000 and check result."]
)
self.assertEqual(
testcases["create user 1000 and check result."]["testcase_def"]["config"]["name"],
"create user and check result."
testcases["create user 1000 and check result."]["testcase_def"][
"config"
]["name"],
"create user and check result.",
)
def test_load_tests_api_file(self):
path = os.path.join(
os.getcwd(), 'tests/api/create_user.yml')
path = os.path.join(os.getcwd(), "tests/api/create_user.yml")
tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"]
api_list = tests_mapping["apis"]
@@ -170,8 +159,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_tests_testcase_file(self):
# absolute file path
path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.json')
path = os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.json")
tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"]
@@ -180,7 +168,7 @@ class TestSuiteLoader(unittest.TestCase):
self.assertIn("get_sign", project_mapping["functions"])
# relative file path
path = 'tests/data/demo_testcase_hardcode.yml'
path = "tests/data/demo_testcase_hardcode.yml"
tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"]
@@ -189,91 +177,75 @@ class TestSuiteLoader(unittest.TestCase):
self.assertIn("get_sign", project_mapping["functions"])
def test_load_tests_testcase_file_2(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase.yml')
testcase_file_path = os.path.join(os.getcwd(), "tests/data/demo_testcase.yml")
tests_mapping = loader.load_cases(testcase_file_path)
testcases = tests_mapping["testcases"]
self.assertIsInstance(testcases, list)
self.assertEqual(testcases[0]["config"]["name"], '123t$var_a')
self.assertIn(
"sum_two",
tests_mapping["project_mapping"]["functions"]
self.assertEqual(testcases[0]["config"]["name"], "123t$var_a")
self.assertIn("sum_two", tests_mapping["project_mapping"]["functions"])
self.assertEqual(
testcases[0]["config"]["variables"]["var_c"], "${sum_two($var_a, $var_b)}"
)
self.assertEqual(
testcases[0]["config"]["variables"]["var_c"],
"${sum_two($var_a, $var_b)}"
)
self.assertEqual(
testcases[0]["config"]["variables"]["PROJECT_KEY"],
"${ENV(PROJECT_KEY)}"
testcases[0]["config"]["variables"]["PROJECT_KEY"], "${ENV(PROJECT_KEY)}"
)
def test_load_tests_testcase_file_with_api_ref(self):
path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
path = os.path.join(os.getcwd(), "tests/data/demo_testcase_layer.yml")
tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"]
self.assertIn('device_sn', testcases_list[0]["config"]["variables"])
self.assertIn("device_sn", testcases_list[0]["config"]["variables"])
self.assertIn("gen_md5", project_mapping["functions"])
self.assertIn("base_url", testcases_list[0]["config"])
test_dict0 = testcases_list[0]["teststeps"][0]
self.assertEqual(
"get token with $user_agent, $app_version",
test_dict0["name"]
)
self.assertEqual("get token with $user_agent, $app_version", test_dict0["name"])
self.assertIn("/api/get-token", test_dict0["api_def"]["request"]["url"])
self.assertIn(
{'eq': ['status_code', 200]},
test_dict0["validate"]
)
self.assertIn({"eq": ["status_code", 200]}, test_dict0["validate"])
def test_load_tests_testsuite_file_with_testcase_ref(self):
path = os.path.join(
os.getcwd(), 'tests/testsuites/create_users.yml')
path = os.path.join(os.getcwd(), "tests/testsuites/create_users.yml")
tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"]
testsuites_list = tests_mapping["testsuites"]
self.assertEqual("create users with uid", testsuites_list[0]["config"]["name"])
self.assertEqual(
"create users with uid",
testsuites_list[0]["config"]["name"]
)
self.assertEqual(
'${gen_random_string(15)}',
testsuites_list[0]["config"]["variables"]['device_sn']
"${gen_random_string(15)}",
testsuites_list[0]["config"]["variables"]["device_sn"],
)
self.assertIn(
"create user 1000 and check result.",
testsuites_list[0]["testcases"]
"create user 1000 and check result.", testsuites_list[0]["testcases"]
)
self.assertEqual(
testsuites_list[0]["testcases"]["create user 1000 and check result."]["testcase_def"]["config"]["name"],
"create user and check result."
testsuites_list[0]["testcases"]["create user 1000 and check result."][
"testcase_def"
]["config"]["name"],
"create user and check result.",
)
def test_load_tests_folder_path(self):
# absolute folder path
path = os.path.join(os.getcwd(), 'tests/data')
path = os.path.join(os.getcwd(), "tests/data")
tests_mapping = loader.load_cases(path)
testcase_list_1 = tests_mapping["testcases"]
self.assertGreater(len(testcase_list_1), 4)
# relative folder path
path = 'tests/data/'
path = "tests/data/"
tests_mapping = loader.load_cases(path)
testcase_list_2 = tests_mapping["testcases"]
self.assertEqual(len(testcase_list_1), len(testcase_list_2))
def test_load_tests_path_not_exist(self):
# absolute folder path
path = os.path.join(os.getcwd(), 'tests/data_not_exist')
path = os.path.join(os.getcwd(), "tests/data_not_exist")
with self.assertRaises(exceptions.FileNotFound):
loader.load_cases(path)
# relative folder path
path = 'tests/data_not_exist'
path = "tests/data_not_exist"
with self.assertRaises(exceptions.FileNotFound):
loader.load_cases(path)
@@ -281,5 +253,11 @@ class TestSuiteLoader(unittest.TestCase):
buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
self.assertIn("gen_md5", self.project_mapping["functions"])
self.assertEqual(self.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH")
self.assertEqual(self.project_mapping["PWD"], os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
self.assertEqual(self.project_mapping["test_path"], os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
self.assertEqual(
self.project_mapping["PWD"],
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
)
self.assertEqual(
self.project_mapping["test_path"],
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
)

View File

@@ -4,7 +4,6 @@ from httprunner.loader import check
class TestLoaderCheck(unittest.TestCase):
def test_is_testcases(self):
data_structure = "path/to/file"
self.assertFalse(check.is_test_content(data_structure))
@@ -12,11 +11,7 @@ class TestLoaderCheck(unittest.TestCase):
self.assertFalse(check.is_test_content(data_structure))
data_structure = {
"project_mapping": {
"PWD": "XXXXX",
"functions": {},
"env": {}
},
"project_mapping": {"PWD": "XXXXX", "functions": {}, "env": {}},
"testcases": [
{ # testcase data structure
"config": {
@@ -27,19 +22,19 @@ class TestLoaderCheck(unittest.TestCase):
"teststeps": [
# test data structure
{
'name': 'test step desc1',
'variables': [], # optional
'extract': {}, # optional
'validate': [],
'request': {
"name": "test step desc1",
"variables": [], # optional
"extract": {}, # optional
"validate": [],
"request": {
"method": "GET",
"url": "https://docs.httprunner.org"
}
"url": "https://docs.httprunner.org",
},
},
# test_dict2 # another test dict
]
],
},
# testcase_dict_2 # another testcase dict
]
],
}
self.assertTrue(check.is_test_content(data_structure))

View File

@@ -7,11 +7,10 @@ from httprunner.loader.buildup import load_test_file
class TestFileLoader(unittest.TestCase):
def test_load_yaml_file_file_format_error(self):
yaml_tmp_file = "tests/data/tmp.yml"
# create empty yaml file
with open(yaml_tmp_file, 'w') as f:
with open(yaml_tmp_file, "w") as f:
f.write("")
with self.assertRaises(exceptions.FileFormatError):
@@ -20,7 +19,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(yaml_tmp_file)
# create invalid format yaml file
with open(yaml_tmp_file, 'w') as f:
with open(yaml_tmp_file, "w") as f:
f.write("abc")
with self.assertRaises(exceptions.FileFormatError):
@@ -31,7 +30,7 @@ class TestFileLoader(unittest.TestCase):
def test_load_json_file_file_format_error(self):
json_tmp_file = "tests/data/tmp.json"
# create empty file
with open(json_tmp_file, 'w') as f:
with open(json_tmp_file, "w") as f:
f.write("")
with self.assertRaises(exceptions.FileFormatError):
@@ -40,7 +39,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file)
# create empty json file
with open(json_tmp_file, 'w') as f:
with open(json_tmp_file, "w") as f:
f.write("{}")
with self.assertRaises(exceptions.FileFormatError):
@@ -49,7 +48,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file)
# create invalid format json file
with open(json_tmp_file, 'w') as f:
with open(json_tmp_file, "w") as f:
f.write("abc")
with self.assertRaises(exceptions.FileFormatError):
@@ -58,62 +57,62 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file)
def test_load_testcases_bad_filepath(self):
testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo')
testcase_file_path = os.path.join(os.getcwd(), "tests/data/demo")
with self.assertRaises(exceptions.FileNotFound):
load.load_file(testcase_file_path)
def test_load_json_testcases(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.json')
os.getcwd(), "tests/data/demo_testcase_hardcode.json"
)
testcases = load.load_file(testcase_file_path)
self.assertEqual(len(testcases), 3)
test = testcases[0]["test"]
self.assertIn('name', test)
self.assertIn('request', test)
self.assertIn('url', test['request'])
self.assertIn('method', test['request'])
self.assertIn("name", test)
self.assertIn("request", test)
self.assertIn("url", test["request"])
self.assertIn("method", test["request"])
def test_load_yaml_testcases(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.yml')
os.getcwd(), "tests/data/demo_testcase_hardcode.yml"
)
testcases = load.load_file(testcase_file_path)
self.assertEqual(len(testcases), 3)
test = testcases[0]["test"]
self.assertIn('name', test)
self.assertIn('request', test)
self.assertIn('url', test['request'])
self.assertIn('method', test['request'])
self.assertIn("name", test)
self.assertIn("request", test)
self.assertIn("url", test["request"])
self.assertIn("method", test["request"])
def test_load_csv_file_one_parameter(self):
csv_file_path = os.path.join(
os.getcwd(), 'tests/data/user_agent.csv')
csv_file_path = os.path.join(os.getcwd(), "tests/data/user_agent.csv")
csv_content = load.load_file(csv_file_path)
self.assertEqual(
csv_content,
[
{'user_agent': 'iOS/10.1'},
{'user_agent': 'iOS/10.2'},
{'user_agent': 'iOS/10.3'}
]
{"user_agent": "iOS/10.1"},
{"user_agent": "iOS/10.2"},
{"user_agent": "iOS/10.3"},
],
)
def test_load_csv_file_multiple_parameters(self):
csv_file_path = os.path.join(
os.getcwd(), 'tests/data/account.csv')
csv_file_path = os.path.join(os.getcwd(), "tests/data/account.csv")
csv_content = load.load_file(csv_file_path)
self.assertEqual(
csv_content,
[
{'username': 'test1', 'password': '111111'},
{'username': 'test2', 'password': '222222'},
{'username': 'test3', 'password': '333333'}
]
{"username": "test1", "password": "111111"},
{"username": "test2", "password": "222222"},
{"username": "test3", "password": "333333"},
],
)
def test_load_folder_files(self):
folder = os.path.join(os.getcwd(), 'tests')
file1 = os.path.join(os.getcwd(), 'tests', 'test_utils.py')
file2 = os.path.join(os.getcwd(), 'tests', 'api', 'reset_all.yml')
folder = os.path.join(os.getcwd(), "tests")
file1 = os.path.join(os.getcwd(), "tests", "test_utils.py")
file2 = os.path.join(os.getcwd(), "tests", "api", "reset_all.yml")
files = load.load_folder_files(folder, recursive=False)
self.assertEqual(files, [])
@@ -129,25 +128,25 @@ class TestFileLoader(unittest.TestCase):
self.assertEqual([], files)
def test_load_dot_env_file(self):
dot_env_path = os.path.join(
os.getcwd(), "tests", ".env"
)
dot_env_path = os.path.join(os.getcwd(), "tests", ".env")
env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertIn("PROJECT_KEY", env_variables_mapping)
self.assertEqual(env_variables_mapping["UserName"], "debugtalk")
def test_load_custom_dot_env_file(self):
dot_env_path = os.path.join(
os.getcwd(), "tests", "data", "test.env"
)
dot_env_path = os.path.join(os.getcwd(), "tests", "data", "test.env")
env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertIn("PROJECT_KEY", env_variables_mapping)
self.assertEqual(env_variables_mapping["UserName"], "test")
self.assertEqual(env_variables_mapping["content_type"], "application/json; charset=UTF-8")
self.assertEqual(
env_variables_mapping["content_type"], "application/json; charset=UTF-8"
)
def test_load_env_path_not_exist(self):
dot_env_path = os.path.join(
os.getcwd(), "tests", "data",
os.getcwd(),
"tests",
"data",
)
env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertEqual(env_variables_mapping, {})

View File

@@ -1,4 +1,3 @@
import os
import unittest
@@ -7,7 +6,6 @@ from httprunner.loader import locate
class TestLoaderLocate(unittest.TestCase):
def test_locate_file(self):
with self.assertRaises(exceptions.FileNotFound):
locate.locate_file(os.getcwd(), "debugtalk.py")
@@ -18,23 +16,21 @@ class TestLoaderLocate(unittest.TestCase):
start_path = os.path.join(os.getcwd(), "tests")
self.assertEqual(
locate.locate_file(start_path, "debugtalk.py"),
os.path.join(
os.getcwd(), "tests/debugtalk.py"
)
os.path.join(os.getcwd(), "tests/debugtalk.py"),
)
self.assertEqual(
locate.locate_file("tests/", "debugtalk.py"),
os.path.join(os.getcwd(), "tests", "debugtalk.py")
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
)
self.assertEqual(
locate.locate_file("tests", "debugtalk.py"),
os.path.join(os.getcwd(), "tests", "debugtalk.py")
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
)
self.assertEqual(
locate.locate_file("tests/base.py", "debugtalk.py"),
os.path.join(os.getcwd(), "tests", "debugtalk.py")
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
)
self.assertEqual(
locate.locate_file("tests/data/demo_testcase.yml", "debugtalk.py"),
os.path.join(os.getcwd(), "tests", "debugtalk.py")
os.path.join(os.getcwd(), "tests", "debugtalk.py"),
)

File diff suppressed because it is too large Load Diff

View File

@@ -7,17 +7,16 @@ from tests.base import ApiServerUnittest
class TestResponse(ApiServerUnittest):
def test_parse_response_object_json(self):
url = "http://127.0.0.1:5000/api/users"
resp = requests.get(url)
resp_obj = response.ResponseObject(resp)
self.assertTrue(hasattr(resp_obj, 'status_code'))
self.assertTrue(hasattr(resp_obj, 'headers'))
self.assertTrue(hasattr(resp_obj, 'content'))
self.assertIn('Content-Type', resp_obj.headers)
self.assertIn('Content-Length', resp_obj.headers)
self.assertIn('success', resp_obj.json)
self.assertTrue(hasattr(resp_obj, "status_code"))
self.assertTrue(hasattr(resp_obj, "headers"))
self.assertTrue(hasattr(resp_obj, "content"))
self.assertIn("Content-Type", resp_obj.headers)
self.assertIn("Content-Length", resp_obj.headers)
self.assertIn("success", resp_obj.json)
def test_parse_response_object_content(self):
url = "http://127.0.0.1:5000/"
@@ -29,19 +28,12 @@ class TestResponse(ApiServerUnittest):
resp = requests.get(url="{}/status/200".format(HTTPBIN_SERVER))
resp_obj = response.ResponseObject(resp)
extract_binds_list = [
{"resp_status_code": "status_code"}
]
extract_binds_list = [{"resp_status_code": "status_code"}]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_status_code"],
200
)
self.assertEqual(extract_binds_dict["resp_status_code"], 200)
extract_binds_list = [
{"resp_status_code": "status_code.xx"}
]
extract_binds_list = [{"resp_status_code": "status_code.xx"}]
with self.assertRaises(exceptions.ParamsError):
resp_obj.extract_response(extract_binds_list)
@@ -53,14 +45,16 @@ class TestResponse(ApiServerUnittest):
{"resp_encoding": "encoding"},
{"resp_ok": "ok"},
{"resp_reason": "reason"},
{"resp_url": "url"}
{"resp_url": "url"},
]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(extract_binds_dict["resp_encoding"], "utf-8")
self.assertEqual(extract_binds_dict["resp_ok"], True)
self.assertEqual(extract_binds_dict["resp_reason"], "OK")
self.assertEqual(extract_binds_dict["resp_url"], "{}/status/200".format(HTTPBIN_SERVER))
self.assertEqual(
extract_binds_dict["resp_url"], "{}/status/200".format(HTTPBIN_SERVER)
)
extract_binds_list = [{"resp_encoding": "encoding.xx"}]
with self.assertRaises(exceptions.ParamsError):
@@ -81,24 +75,15 @@ class TestResponse(ApiServerUnittest):
def test_extract_response_cookies(self):
resp = requests.get(
url="{}/cookies".format(HTTPBIN_SERVER),
headers={
"accept": "application/json"
}
headers={"accept": "application/json"},
)
resp_obj = response.ResponseObject(resp)
extract_binds_list = [
{"resp_cookies": "cookies"}
]
extract_binds_list = [{"resp_cookies": "cookies"}]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_cookies"],
{}
)
self.assertEqual(extract_binds_dict["resp_cookies"], {})
extract_binds_list = [
{"resp_cookies": "cookies.xx"}
]
extract_binds_list = [{"resp_cookies": "cookies.xx"}]
with self.assertRaises(exceptions.ExtractFailure):
resp_obj.extract_response(extract_binds_list)
@@ -106,22 +91,20 @@ class TestResponse(ApiServerUnittest):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
json={
'success': False,
"success": False,
"person": {
"name": {
"first_name": "Leo",
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
}
"cities": ["Guangzhou", "Shenzhen"],
},
},
)
resp_obj = response.ResponseObject(resp)
extract_binds_list = [
{"resp_elapsed": "elapsed"}
]
extract_binds_list = [{"resp_elapsed": "elapsed"}]
with self.assertRaises(exceptions.ParamsError):
resp_obj.extract_response(extract_binds_list)
@@ -129,7 +112,7 @@ class TestResponse(ApiServerUnittest):
{"resp_elapsed_microseconds": "elapsed.microseconds"},
{"resp_elapsed_seconds": "elapsed.seconds"},
{"resp_elapsed_days": "elapsed.days"},
{"resp_elapsed_total_seconds": "elapsed.total_seconds"}
{"resp_elapsed_total_seconds": "elapsed.total_seconds"},
]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertGreater(extract_binds_dict["resp_elapsed_microseconds"], 1000)
@@ -137,9 +120,7 @@ class TestResponse(ApiServerUnittest):
self.assertEqual(extract_binds_dict["resp_elapsed_days"], 0)
self.assertGreater(extract_binds_dict["resp_elapsed_total_seconds"], 0)
extract_binds_list = [
{"resp_elapsed": "elapsed.years"}
]
extract_binds_list = [{"resp_elapsed": "elapsed.years"}]
with self.assertRaises(exceptions.ParamsError):
resp_obj.extract_response(extract_binds_list)
@@ -150,16 +131,16 @@ class TestResponse(ApiServerUnittest):
extract_binds_list = [
{"resp_headers": "headers"},
{"resp_headers_content_type": "headers.Content-Type"},
{"resp_headers_content_type_lowercase": "headers.content-type"}
{"resp_headers_content_type_lowercase": "headers.content-type"},
]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertIn("Content-Type", extract_binds_dict["resp_headers"])
self.assertIn("text/html", extract_binds_dict["resp_headers_content_type"])
self.assertIn("text/html", extract_binds_dict["resp_headers_content_type_lowercase"])
self.assertIn(
"text/html", extract_binds_dict["resp_headers_content_type_lowercase"]
)
extract_binds_list = [
{"resp_headers_xxx": "headers.xxx"}
]
extract_binds_list = [{"resp_headers_xxx": "headers.xxx"}]
with self.assertRaises(exceptions.ExtractFailure):
resp_obj.extract_response(extract_binds_list)
@@ -167,16 +148,16 @@ class TestResponse(ApiServerUnittest):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
json={
'success': False,
"success": False,
"person": {
"name": {
"first_name": "Leo",
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
}
"cities": ["Guangzhou", "Shenzhen"],
},
},
)
# resp.json()
# {
@@ -218,51 +199,31 @@ class TestResponse(ApiServerUnittest):
{"resp_content_content_success": "content.json.success"},
{"resp_content_text_success": "text.json.success"},
{"resp_content_person_first_name": "content.json.person.name.first_name"},
{"resp_content_cities_1": "content.json.person.cities.1"}
{"resp_content_cities_1": "content.json.person.cities.1"},
]
resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_headers_content_type"],
"application/json"
)
self.assertEqual(
extract_binds_dict["resp_content_body_success"],
False
)
self.assertEqual(
extract_binds_dict["resp_content_content_success"],
False
)
self.assertEqual(
extract_binds_dict["resp_content_text_success"],
False
)
self.assertEqual(
extract_binds_dict["resp_content_person_first_name"],
"Leo"
)
self.assertEqual(
extract_binds_dict["resp_content_cities_1"],
"Shenzhen"
extract_binds_dict["resp_headers_content_type"], "application/json"
)
self.assertEqual(extract_binds_dict["resp_content_body_success"], False)
self.assertEqual(extract_binds_dict["resp_content_content_success"], False)
self.assertEqual(extract_binds_dict["resp_content_text_success"], False)
self.assertEqual(extract_binds_dict["resp_content_person_first_name"], "Leo")
self.assertEqual(extract_binds_dict["resp_content_cities_1"], "Shenzhen")
def test_extract_response_body_html(self):
resp = requests.get(url=HTTPBIN_SERVER)
resp_obj = response.ResponseObject(resp)
extract_binds_list = [
{"resp_content": "content"}
]
extract_binds_list = [{"resp_content": "content"}]
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertIsInstance(extract_binds_dict["resp_content"], basestring)
self.assertIn("httpbin.org", extract_binds_dict["resp_content"])
extract_binds_list = [
{"resp_content": "content.xxx"}
]
extract_binds_list = [{"resp_content": "content.xxx"}]
with self.assertRaises(exceptions.ExtractFailure):
resp_obj.extract_response(extract_binds_list)
@@ -272,7 +233,7 @@ class TestResponse(ApiServerUnittest):
extract_binds_list = [
{"resp_others_encoding": "encoding"},
{"resp_others_history": "history"}
{"resp_others_history": "history"},
]
with self.assertRaises(exceptions.ParamsError):
resp_obj.extract_response(extract_binds_list)
@@ -281,21 +242,19 @@ class TestResponse(ApiServerUnittest):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
json={
'success': False,
"success": False,
"person": {
"name": {
"first_name": "Leo",
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
}
"cities": ["Guangzhou", "Shenzhen"],
},
},
)
extract_binds_list = [
{"resp_content_dict_key_error": "content.not_exist"}
]
extract_binds_list = [{"resp_content_dict_key_error": "content.not_exist"}]
resp_obj = response.ResponseObject(resp)
with self.assertRaises(exceptions.ExtractFailure):
@@ -310,80 +269,49 @@ class TestResponse(ApiServerUnittest):
resp_obj.extract_response(extract_binds_list)
def test_extract_response_json_string(self):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
data="abc"
)
resp = requests.post(url="{}/anything".format(HTTPBIN_SERVER), data="abc")
extract_binds_list = [
{"resp_content_body": "content.data"}
]
extract_binds_list = [{"resp_content_body": "content.data"}]
resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_content_body"],
"abc"
)
self.assertEqual(extract_binds_dict["resp_content_body"], "abc")
def test_extract_text_response(self):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
data="LB123abcRB789"
url="{}/anything".format(HTTPBIN_SERVER), data="LB123abcRB789"
)
extract_binds_list = [
{"resp_content_key1": "LB123(.*)RB789"},
{"resp_content_key2": "LB[\d]*(.*)RB[\d]*"},
{"resp_content_key3": "LB[\d]*(.*)9"}
{"resp_content_key3": "LB[\d]*(.*)9"},
]
resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_content_key1"],
"abc"
)
self.assertEqual(
extract_binds_dict["resp_content_key2"],
"abc"
)
self.assertEqual(
extract_binds_dict["resp_content_key3"],
"abcRB78"
)
self.assertEqual(extract_binds_dict["resp_content_key1"], "abc")
self.assertEqual(extract_binds_dict["resp_content_key2"], "abc")
self.assertEqual(extract_binds_dict["resp_content_key3"], "abcRB78")
def test_extract_text_response_exception(self):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
data="LB123abcRB789"
url="{}/anything".format(HTTPBIN_SERVER), data="LB123abcRB789"
)
extract_binds_list = [
{"resp_content_key1": "LB123.*RB789"}
]
extract_binds_list = [{"resp_content_key1": "LB123.*RB789"}]
resp_obj = response.ResponseObject(resp)
with self.assertRaises(exceptions.ParamsError):
resp_obj.extract_response(extract_binds_list)
def test_extract_response_empty(self):
resp = requests.post(
url="{}/anything".format(HTTPBIN_SERVER),
data="abc"
)
resp = requests.post(url="{}/anything".format(HTTPBIN_SERVER), data="abc")
extract_binds_list = [
{"resp_content_body": "content.data"}
]
extract_binds_list = [{"resp_content_body": "content.data"}]
resp_obj = response.ResponseObject(resp)
extract_binds_dict = resp_obj.extract_response(extract_binds_list)
self.assertEqual(
extract_binds_dict["resp_content_body"],
'abc'
)
self.assertEqual(extract_binds_dict["resp_content_body"], "abc")
extract_binds_list = [
{"resp_content_body": "content.data.def"}
]
extract_binds_list = [{"resp_content_body": "content.data.def"}]
resp_obj = response.ResponseObject(resp)
with self.assertRaises(exceptions.ExtractFailure):
resp_obj.extract_response(extract_binds_list)

View File

@@ -7,16 +7,11 @@ from tests.base import ApiServerUnittest
class TestRunner(ApiServerUnittest):
def setUp(self):
project_mapping = loader.load_project_data(os.path.join(os.getcwd(), "tests"))
self.debugtalk_functions = project_mapping["functions"]
config = {
"name": "XXX",
"base_url": "http://127.0.0.1",
"verify": False
}
config = {"name": "XXX", "base_url": "http://127.0.0.1", "verify": False}
self.test_runner = runner.Runner(config)
self.reset_all()
@@ -27,10 +22,8 @@ class TestRunner(ApiServerUnittest):
def test_run_single_testcase(self):
testcase_file_path_list = [
os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.yml'),
os.path.join(
os.getcwd(), 'tests/data/demo_testcase_hardcode.json')
os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.yml"),
os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.json"),
]
for testcase_file_path in testcase_file_path_list:
@@ -50,14 +43,8 @@ class TestRunner(ApiServerUnittest):
"config": {
"name": "basic test with httpbin",
"base_url": HTTPBIN_SERVER,
"setup_hooks": [
"${sleep(0.5)}",
"${hook_print(setup)}"
],
"teardown_hooks": [
"${sleep(1)}",
"${hook_print(teardown)}"
]
"setup_hooks": ["${sleep(0.5)}", "${hook_print(setup)}"],
"teardown_hooks": ["${sleep(1)}", "${hook_print(teardown)}"],
},
"teststeps": [
{
@@ -70,24 +57,20 @@ class TestRunner(ApiServerUnittest):
"user_agent": "iOS/10.3",
"device_sn": "HZfFBh6tU59EdXJ",
"os_platform": "ios",
"app_version": "2.8.6"
"app_version": "2.8.6",
},
"json": {
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad"
}
},
},
"validate": [
{"check": "status_code", "expect": 200}
]
"validate": [{"check": "status_code", "expect": 200}],
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -107,7 +90,7 @@ class TestRunner(ApiServerUnittest):
{
"config": {
"name": "basic test with httpbin",
"base_url": HTTPBIN_SERVER
"base_url": HTTPBIN_SERVER,
},
"teststeps": [
{
@@ -116,27 +99,18 @@ class TestRunner(ApiServerUnittest):
"request": {
"url": "/anything",
"method": "POST",
"headers": {
"user_agent": "iOS/10.3",
"os_platform": "ios"
},
"data": "a=1&b=2"
"headers": {"user_agent": "iOS/10.3", "os_platform": "ios"},
"data": "a=1&b=2",
},
"setup_hooks": [
{"total": "${sum_two(1, 5)}"}
],
"validate": [
{"check": "status_code", "expect": 200}
]
"setup_hooks": [{"total": "${sum_two(1, 5)}"}],
"validate": [{"check": "status_code", "expect": 200}],
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -151,7 +125,7 @@ class TestRunner(ApiServerUnittest):
{
"config": {
"name": "basic test with httpbin",
"base_url": HTTPBIN_SERVER
"base_url": HTTPBIN_SERVER,
},
"teststeps": [
{
@@ -162,29 +136,25 @@ class TestRunner(ApiServerUnittest):
"method": "POST",
"headers": {
"content-type": "application/json",
"user_agent": "iOS/10.3"
"user_agent": "iOS/10.3",
},
"json": {
"os_platform": "ios",
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad"
}
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad",
},
},
"setup_hooks": [
"${modify_request_json($request, android)}"
],
"setup_hooks": ["${modify_request_json($request, android)}"],
"validate": [
{"check": "status_code", "expect": 200},
{"check": "content.json.os_platform", "expect": "android"}
]
{"check": "content.json.os_platform", "expect": "android"},
],
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -194,9 +164,7 @@ class TestRunner(ApiServerUnittest):
def test_run_testcase_with_teardown_hooks_success(self):
testcases = [
{
"config": {
"name": "basic test with httpbin"
},
"config": {"name": "basic test with httpbin"},
"teststeps": [
{
"name": "get token",
@@ -208,25 +176,23 @@ class TestRunner(ApiServerUnittest):
"user_agent": "iOS/10.3",
"device_sn": "HZfFBh6tU59EdXJ",
"os_platform": "ios",
"app_version": "2.8.6"
"app_version": "2.8.6",
},
"json": {
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad"
}
},
},
"validate": [
{"check": "status_code", "expect": 200}
"validate": [{"check": "status_code", "expect": 200}],
"teardown_hooks": [
"${teardown_hook_sleep_N_secs($response, 2)}"
],
"teardown_hooks": ["${teardown_hook_sleep_N_secs($response, 2)}"]
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -241,9 +207,7 @@ class TestRunner(ApiServerUnittest):
def test_run_testcase_with_teardown_hooks_fail(self):
testcases = [
{
"config": {
"name": "basic test with httpbin"
},
"config": {"name": "basic test with httpbin"},
"teststeps": [
{
"name": "get token",
@@ -255,25 +219,23 @@ class TestRunner(ApiServerUnittest):
"user_agent": "iOS/10.3",
"device_sn": "HZfFBh6tU59EdXJ",
"os_platform": "ios",
"app_version": "2.8.6"
"app_version": "2.8.6",
},
"json": {
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad"
}
},
},
"validate": [
{"check": "status_code", "expect": 404}
"validate": [{"check": "status_code", "expect": 404}],
"teardown_hooks": [
"${teardown_hook_sleep_N_secs($response, 2)}"
],
"teardown_hooks": ["${teardown_hook_sleep_N_secs($response, 2)}"]
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -287,7 +249,8 @@ class TestRunner(ApiServerUnittest):
def test_bugfix_type_match(self):
testcase_file_path = os.path.join(
os.getcwd(), 'tests/data/bugfix_type_match.yml')
os.getcwd(), "tests/data/bugfix_type_match.yml"
)
tests_mapping = loader.load_cases(testcase_file_path)
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -309,28 +272,38 @@ class TestRunner(ApiServerUnittest):
"user_agent": "iOS/10.3",
"device_sn": "HZfFBh6tU59EdXJ",
"os_platform": "ios",
"app_version": "2.8.6"
"app_version": "2.8.6",
},
"json": {
"sign": "5188962c489d1a35effa99e9346dd5efd4fdabad"
}
},
},
"validate": [
{"check": "status_code", "expect": 200},
{"check": "elapsed.seconds", "comparator": "lt", "expect": 1},
{
"check": "elapsed.seconds",
"comparator": "lt",
"expect": 1,
},
{"check": "elapsed.days", "comparator": "eq", "expect": 0},
{"check": "elapsed.microseconds", "comparator": "gt", "expect": 1000},
{"check": "elapsed.total_seconds", "comparator": "lt", "expect": 1}
]
{
"check": "elapsed.microseconds",
"comparator": "gt",
"expect": 1000,
},
{
"check": "elapsed.total_seconds",
"comparator": "lt",
"expect": 1,
},
],
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]
@@ -343,7 +316,7 @@ class TestRunner(ApiServerUnittest):
"config": {
"name": "basic test with httpbin",
"base_url": HTTPBIN_SERVER,
"variables": "${gen_variables()}"
"variables": "${gen_variables()}",
},
"teststeps": [
{
@@ -352,24 +325,17 @@ class TestRunner(ApiServerUnittest):
"request": {
"url": "/anything",
"method": "POST",
"headers": {
"user_agent": "iOS/10.3",
"os_platform": "ios"
},
"data": "a=1&b=2"
"headers": {"user_agent": "iOS/10.3", "os_platform": "ios"},
"data": "a=1&b=2",
},
"validate": [
{"check": "status_code", "expect": 200}
]
"validate": [{"check": "status_code", "expect": 200}],
}
]
],
}
]
tests_mapping = {
"project_mapping": {
"functions": self.debugtalk_functions
},
"testcases": testcases
"project_mapping": {"functions": self.debugtalk_functions},
"testcases": testcases,
}
parsed_testcases = parser.parse_tests(tests_mapping)
parsed_testcase = parsed_testcases[0]

View File

@@ -7,12 +7,9 @@ from tests.base import ApiServerUnittest
class TestUtils(ApiServerUnittest):
def test_set_os_environ(self):
self.assertNotIn("abc", os.environ)
variables_mapping = {
"abc": "123"
}
variables_mapping = {"abc": "123"}
utils.set_os_environ(variables_mapping)
self.assertIn("abc", os.environ)
self.assertEqual(os.environ["abc"], "123")
@@ -26,8 +23,8 @@ class TestUtils(ApiServerUnittest):
"last_name": "Lee",
},
"age": 29,
"cities": ["Guangzhou", "Shenzhen"]
}
"cities": ["Guangzhou", "Shenzhen"],
},
}
query = "ids.2"
result = utils.query_json(json_content, query)
@@ -63,6 +60,7 @@ class TestUtils(ApiServerUnittest):
def current_validators(self):
from httprunner.builtin import comparators
functions_mapping = loader.load.load_module_functions(comparators)
functions_mapping["equals"](None, None)
@@ -80,17 +78,17 @@ class TestUtils(ApiServerUnittest):
functions_mapping["not_equals"](123, "123")
functions_mapping["length_equals"]("123", 3)
# Because the Numbers in a CSV file are by default treated as strings,
# Because the Numbers in a CSV file are by default treated as strings,
# you need to convert them to Numbers, and we'll test that out here.
functions_mapping["length_equals"]("123", '3')
functions_mapping["length_equals"]("123", "3")
with self.assertRaises(AssertionError):
functions_mapping["length_equals"]("123", 'abc')
functions_mapping["length_equals"]("123", "abc")
functions_mapping["length_greater_than"]("123", 2)
functions_mapping["length_greater_than_or_equals"]("123", 3)
functions_mapping["contains"]("123abc456", "3ab")
functions_mapping["contains"](['1', '2'], "1")
functions_mapping["contains"]({'a':1, 'b':2}, "a")
functions_mapping["contains"](["1", "2"], "1")
functions_mapping["contains"]({"a": 1, "b": 2}, "a")
functions_mapping["contained_by"]("3ab", "123abc456")
functions_mapping["regex_match"]("123abc456", "^123\w+456$")
@@ -119,11 +117,8 @@ class TestUtils(ApiServerUnittest):
"Request": {
"url": "http://127.0.0.1:5000",
"METHOD": "POST",
"Headers": {
"Accept": "application/json",
"User-Agent": "ios/9.3"
}
}
"Headers": {"Accept": "application/json", "User-Agent": "ios/9.3"},
},
}
new_dict = utils.lower_test_dict_keys(origin_dict)
self.assertIn("name", new_dict)
@@ -133,10 +128,7 @@ class TestUtils(ApiServerUnittest):
self.assertIn("Accept", new_dict["request"]["headers"])
self.assertIn("User-Agent", new_dict["request"]["headers"])
origin_dict = {
"Name": "test",
"Request": "$default_request"
}
origin_dict = {"Name": "test", "Request": "$default_request"}
new_dict = utils.lower_test_dict_keys(origin_dict)
self.assertIn("$default_request", new_dict["request"])
@@ -144,10 +136,7 @@ class TestUtils(ApiServerUnittest):
request_dict = {
"url": "http://127.0.0.1:5000",
"METHOD": "POST",
"Headers": {
"Accept": "application/json",
"User-Agent": "ios/9.3"
}
"Headers": {"Accept": "application/json", "User-Agent": "ios/9.3"},
}
new_request_dict = utils.lower_dict_keys(request_dict)
self.assertIn("method", new_request_dict)
@@ -164,10 +153,7 @@ class TestUtils(ApiServerUnittest):
self.assertEqual(None, request_dict)
def test_ensure_mapping_format(self):
map_list = [
{"a": 1},
{"b": 2}
]
map_list = [{"a": 1}, {"b": 2}]
ordered_dict = utils.ensure_mapping_format(map_list)
self.assertIsInstance(ordered_dict, dict)
self.assertIn("a", ordered_dict)
@@ -175,7 +161,9 @@ class TestUtils(ApiServerUnittest):
def test_extend_variables(self):
raw_variables = [{"var1": "val1"}, {"var2": "val2"}]
override_variables = [{"var1": "val111"}, {"var3": "val3"}]
extended_variables_mapping = utils.extend_variables(raw_variables, override_variables)
extended_variables_mapping = utils.extend_variables(
raw_variables, override_variables
)
self.assertEqual(extended_variables_mapping["var1"], "val111")
self.assertEqual(extended_variables_mapping["var2"], "val2")
self.assertEqual(extended_variables_mapping["var3"], "val3")
@@ -183,25 +171,27 @@ class TestUtils(ApiServerUnittest):
def test_extend_variables_fix(self):
raw_variables = [{"var1": "val1"}, {"var2": "val2"}]
override_variables = {}
extended_variables_mapping = utils.extend_variables(raw_variables, override_variables)
extended_variables_mapping = utils.extend_variables(
raw_variables, override_variables
)
self.assertEqual(extended_variables_mapping["var1"], "val1")
def test_deepcopy_dict(self):
data = {
'a': 1,
'b': [2, 4],
'c': lambda x: x+1,
'd': open('LICENSE'),
'f': {
'f1': {'a1': 2},
'f2': io.open('LICENSE', 'rb'),
}
"a": 1,
"b": [2, 4],
"c": lambda x: x + 1,
"d": open("LICENSE"),
"f": {
"f1": {"a1": 2},
"f2": io.open("LICENSE", "rb"),
},
}
new_data = utils.deepcopy_dict(data)
data["a"] = 0
self.assertEqual(new_data["a"], 1)
data["f"]["f1"] = 123
self.assertEqual(new_data["f"]["f1"], {'a1': 2})
self.assertEqual(new_data["f"]["f1"], {"a1": 2})
self.assertNotEqual(id(new_data["b"]), id(data["b"]))
self.assertEqual(id(new_data["c"]), id(data["c"]))
# self.assertEqual(id(new_data["d"]), id(data["d"]))
@@ -218,41 +208,24 @@ class TestUtils(ApiServerUnittest):
shutil.rmtree(project_name)
def test_cartesian_product_one(self):
parameters_content_list = [
[
{"a": 1},
{"a": 2}
]
]
parameters_content_list = [[{"a": 1}, {"a": 2}]]
product_list = utils.gen_cartesian_product(*parameters_content_list)
self.assertEqual(
product_list,
[
{"a": 1},
{"a": 2}
]
)
self.assertEqual(product_list, [{"a": 1}, {"a": 2}])
def test_cartesian_product_multiple(self):
parameters_content_list = [
[
{"a": 1},
{"a": 2}
],
[
{"x": 111, "y": 112},
{"x": 121, "y": 122}
]
[{"a": 1}, {"a": 2}],
[{"x": 111, "y": 112}, {"x": 121, "y": 122}],
]
product_list = utils.gen_cartesian_product(*parameters_content_list)
self.assertEqual(
product_list,
[
{'a': 1, 'x': 111, 'y': 112},
{'a': 1, 'x': 121, 'y': 122},
{'a': 2, 'x': 111, 'y': 112},
{'a': 2, 'x': 121, 'y': 122}
]
{"a": 1, "x": 111, "y": 112},
{"a": 1, "x": 121, "y": 122},
{"a": 2, "x": 111, "y": 112},
{"a": 2, "x": 121, "y": 122},
],
)
def test_cartesian_product_empty(self):
@@ -261,15 +234,7 @@ class TestUtils(ApiServerUnittest):
self.assertEqual(product_list, [])
def test_print_info(self):
info_mapping = {
"a": 1,
"t": (1, 2),
"b": {
"b1": 123
},
"c": None,
"d": [4, 5]
}
info_mapping = {"a": 1, "t": (1, 2), "b": {"b1": 123}, "c": None, "d": [4, 5]}
utils.print_info(info_mapping)
def test_prepare_dump_json_file_path_for_folder(self):
@@ -277,11 +242,13 @@ class TestUtils(ApiServerUnittest):
project_working_directory = os.path.join(os.getcwd(), "tests")
project_mapping = {
"PWD": project_working_directory,
"test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c")
"test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c"),
}
self.assertEqual(
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
os.path.join(project_working_directory, "logs", "httpbin/a.b.c/all.loaded.json")
os.path.join(
project_working_directory, "logs", "httpbin/a.b.c/all.loaded.json"
),
)
def test_prepare_dump_json_file_path_for_file(self):
@@ -289,19 +256,23 @@ class TestUtils(ApiServerUnittest):
project_working_directory = os.path.join(os.getcwd(), "tests")
project_mapping = {
"PWD": project_working_directory,
"test_path": os.path.join(os.getcwd(), "tests", "httpbin", "a.b.c", "rpc.yml")
"test_path": os.path.join(
os.getcwd(), "tests", "httpbin", "a.b.c", "rpc.yml"
),
}
self.assertEqual(
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
os.path.join(project_working_directory, "logs", "httpbin/a.b.c/rpc.loaded.json")
os.path.join(
project_working_directory, "logs", "httpbin/a.b.c/rpc.loaded.json"
),
)
def test_prepare_dump_json_file_path_for_passed_testcase(self):
project_working_directory = os.path.join(os.getcwd(), "tests")
project_mapping = {
"PWD": project_working_directory
}
project_mapping = {"PWD": project_working_directory}
self.assertEqual(
utils.prepare_dump_json_file_abs_path(project_mapping, "loaded"),
os.path.join(project_working_directory, "logs", "tests_mapping.loaded.json")
os.path.join(
project_working_directory, "logs", "tests_mapping.loaded.json"
),
)