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 #1217: reload debugtalk.py if loaded
- fix #1246: catch exceptions caused by GA report failure - fix #1246: catch exceptions caused by GA report failure
- refactor: format code with black
## 2.5.8 (2022-03-23) ## 2.5.8 (2022-03-23)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -23,10 +23,10 @@ import sys
_ver = sys.version_info _ver = sys.version_info
#: Python 2.x? #: Python 2.x?
is_py2 = (_ver[0] == 2) is_py2 = _ver[0] == 2
#: Python 3.x? #: 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): class SessionContext(object):
""" HttpRunner session, store runtime variables. """HttpRunner session, store runtime variables.
Examples: Examples:
>>> variables = {"SECRET_KEY": "DebugTalk"} >>> variables = {"SECRET_KEY": "DebugTalk"}
@@ -16,12 +16,14 @@ class SessionContext(object):
def __init__(self, variables=None): def __init__(self, variables=None):
variables_mapping = utils.ensure_mapping_format(variables or {}) 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.test_variables_mapping = {}
self.init_test_variables() self.init_test_variables()
def init_test_variables(self, variables_mapping=None): 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. variables_mapping will be evaluated first.
Args: Args:
@@ -45,20 +47,19 @@ class SessionContext(object):
self.test_variables_mapping.update(self.session_variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
def update_test_variables(self, variable_name, variable_value): 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 self.test_variables_mapping[variable_name] = variable_value
def update_session_variables(self, variables_mapping): def update_session_variables(self, variables_mapping):
""" update session with extracted variables mapping. """update session with extracted variables mapping.
these variables are valid in the whole running session. these variables are valid in the whole running session.
""" """
variables_mapping = utils.ensure_mapping_format(variables_mapping) variables_mapping = utils.ensure_mapping_format(variables_mapping)
self.session_variables_mapping.update(variables_mapping) self.session_variables_mapping.update(variables_mapping)
self.test_variables_mapping.update(self.session_variables_mapping) self.test_variables_mapping.update(self.session_variables_mapping)
def eval_content(self, content): def eval_content(self, content):
""" evaluate content recursively, take effect on each variable and function in 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. content may be in any data structure, include dict, list, tuple, number, string, etc.
""" """
return parser.parse_lazy_data(content, self.test_variables_mapping) return parser.parse_lazy_data(content, self.test_variables_mapping)

View File

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

View File

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

View File

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

View File

@@ -61,7 +61,7 @@ from httprunner.exceptions import ParamsError
def prepare_upload_test(test_dict): def prepare_upload_test(test_dict):
""" preprocess for upload test """preprocess for upload test
replace `upload` info with MultipartEncoder replace `upload` info with MultipartEncoder
Args: Args:
@@ -96,14 +96,15 @@ def prepare_upload_test(test_dict):
test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}" test_dict["variables"]["m_encoder"] = "${multipart_encoder(" + params_str + ")}"
test_dict["request"].setdefault("headers", {}) 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" test_dict["request"]["data"] = "$m_encoder"
def multipart_encoder(**kwargs): def multipart_encoder(**kwargs):
""" initialize MultipartEncoder with uploading fields. """initialize MultipartEncoder with uploading fields."""
"""
def get_filetype(file_path): def get_filetype(file_path):
file_type = filetype.guess(file_path) file_type = filetype.guess(file_path)
@@ -122,6 +123,7 @@ def multipart_encoder(**kwargs):
else: else:
# value is not absolute file path, check if it is relative file path # value is not absolute file path, check if it is relative file path
from httprunner.loader import get_pwd from httprunner.loader import get_pwd
_file_path = os.path.join(get_pwd(), value) _file_path = os.path.join(get_pwd(), value)
is_exists_file = os.path.isfile(_file_path) is_exists_file = os.path.isfile(_file_path)
@@ -130,7 +132,7 @@ def multipart_encoder(**kwargs):
filename = os.path.basename(_file_path) filename = os.path.basename(_file_path)
mime_type = get_filetype(_file_path) mime_type = get_filetype(_file_path)
# TODO: fix ResourceWarning for unclosed file # 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) fields_dict[key] = (filename, file_handler, mime_type)
else: else:
fields_dict[key] = value fields_dict[key] = value
@@ -139,6 +141,5 @@ def multipart_encoder(**kwargs):
def multipart_content_type(m_encoder): def multipart_content_type(m_encoder):
""" prepare Content-Type for request headers """prepare Content-Type for request headers"""
"""
return m_encoder.content_type 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.check import is_test_path, is_test_content, JsonSchemaChecker
from httprunner.loader.locate import get_project_working_directory as get_pwd, \ from httprunner.loader.locate import (
init_project_working_directory as init_pwd 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.load import load_csv_file, load_builtin_functions
from httprunner.loader.buildup import load_cases, load_project_data from httprunner.loader.buildup import load_cases, load_project_data
@@ -23,5 +25,5 @@ __all__ = [
"load_csv_file", "load_csv_file",
"load_builtin_functions", "load_builtin_functions",
"load_project_data", "load_project_data",
"load_cases" "load_cases",
] ]

View File

@@ -4,18 +4,22 @@ import sys
from httprunner import exceptions, logger, utils from httprunner import exceptions, logger, utils
from httprunner.loader.check import JsonSchemaChecker from httprunner.loader.check import JsonSchemaChecker
from httprunner.loader.load import load_module_functions, load_file, load_dot_env_file, \ from httprunner.loader.load import (
load_folder_files load_module_functions,
from httprunner.loader.locate import init_project_working_directory, get_project_working_directory 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 = { tests_def_mapping = {"api": {}, "testcases": {}}
"api": {},
"testcases": {}
}
def load_debugtalk_functions(): 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. debugtalk.py should be located in project working directory.
Returns: Returns:
@@ -27,8 +31,8 @@ def load_debugtalk_functions():
""" """
# load debugtalk.py module # load debugtalk.py module
if sys.modules.get('debugtalk'): if sys.modules.get("debugtalk"):
imported_module = importlib.reload(sys.modules['debugtalk']) imported_module = importlib.reload(sys.modules["debugtalk"])
else: else:
imported_module = importlib.import_module("debugtalk") imported_module = importlib.import_module("debugtalk")
@@ -36,7 +40,7 @@ def load_debugtalk_functions():
def __extend_with_api_ref(raw_testinfo): def __extend_with_api_ref(raw_testinfo):
""" extend with api reference """extend with api reference
Raises: Raises:
exceptions.ApiNotFound: api not found exceptions.ApiNotFound: api not found
@@ -68,17 +72,13 @@ def __extend_with_api_ref(raw_testinfo):
def __extend_with_testcase_ref(raw_testinfo): def __extend_with_testcase_ref(raw_testinfo):
""" extend with testcase reference """extend with testcase reference"""
"""
testcase_path = raw_testinfo["testcase"] testcase_path = raw_testinfo["testcase"]
if testcase_path not in tests_def_mapping["testcases"]: if testcase_path not in tests_def_mapping["testcases"]:
# make compatible with Windows/Linux # make compatible with Windows/Linux
pwd = get_project_working_directory() pwd = get_project_working_directory()
testcase_path = os.path.join( testcase_path = os.path.join(pwd, *testcase_path.split("/"))
pwd,
*testcase_path.split("/")
)
loaded_testcase = load_file(testcase_path) loaded_testcase = load_file(testcase_path)
if isinstance(loaded_testcase, list): if isinstance(loaded_testcase, list):
@@ -89,7 +89,8 @@ def __extend_with_testcase_ref(raw_testinfo):
testcase_dict = load_testcase_v2(loaded_testcase) testcase_dict = load_testcase_v2(loaded_testcase)
else: else:
raise exceptions.FileFormatError( raise exceptions.FileFormatError(
"Invalid format testcase: {}".format(testcase_path)) "Invalid format testcase: {}".format(testcase_path)
)
tests_def_mapping["testcases"][testcase_path] = testcase_dict tests_def_mapping["testcases"][testcase_path] = testcase_dict
else: else:
@@ -99,7 +100,7 @@ def __extend_with_testcase_ref(raw_testinfo):
def load_teststep(raw_testinfo): def load_teststep(raw_testinfo):
""" load testcase step content. """load testcase step content.
teststep maybe defined directly, or reference api/testcase. teststep maybe defined directly, or reference api/testcase.
Args: Args:
@@ -151,7 +152,7 @@ def load_teststep(raw_testinfo):
def load_testcase(raw_testcase): def load_testcase(raw_testcase):
""" load testcase with api/testcase references. """load testcase with api/testcase references.
Args: Args:
raw_testcase (list): raw testcase content loaded from JSON/YAML file: 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)) tests.append(load_teststep(test_block))
else: else:
logger.log_warning( 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 { return {"config": config, "teststeps": tests}
"config": config,
"teststeps": tests
}
def load_testcase_v2(raw_testcase): def load_testcase_v2(raw_testcase):
""" load testcase in format version 2. """load testcase in format version 2.
Args: Args:
raw_testcase (dict): raw testcase content loaded from JSON/YAML file: 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) JsonSchemaChecker.validate_testcase_v2_format(raw_testcase)
raw_teststeps = raw_testcase.pop("teststeps") raw_teststeps = raw_testcase.pop("teststeps")
raw_testcase["teststeps"] = [ raw_testcase["teststeps"] = [load_teststep(teststep) for teststep in raw_teststeps]
load_teststep(teststep)
for teststep in raw_teststeps
]
return raw_testcase return raw_testcase
def load_testsuite(raw_testsuite): def load_testsuite(raw_testsuite):
""" load testsuite with testcase references. """load testsuite with testcase references.
support two different formats. support two different formats.
Args: Args:
@@ -315,7 +312,7 @@ def load_testsuite(raw_testsuite):
def load_test_file(path): def load_test_file(path):
""" load test file, file maybe testcase/testsuite/api """load test file, file maybe testcase/testsuite/api
Args: Args:
path (str): test file path path (str): test file path
@@ -390,7 +387,7 @@ def load_test_file(path):
def load_project_data(test_path, dot_env_path=None): 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 api/testcases folder is relative to project_working_directory
Args: Args:
@@ -402,7 +399,9 @@ def load_project_data(test_path, dot_env_path=None):
environments and debugtalk.py functions. 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 = {} project_mapping = {}
@@ -428,7 +427,7 @@ def load_project_data(test_path, dot_env_path=None):
def load_cases(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: Args:
path (str): testcase/testsuite file/foler path. path (str): testcase/testsuite file/foler path.
@@ -481,9 +480,7 @@ def load_cases(path, dot_env_path=None):
""" """
tests_mapping = { tests_mapping = {"project_mapping": load_project_data(path, dot_env_path)}
"project_mapping": load_project_data(path, dot_env_path)
}
def __load_file_content(path): def __load_file_content(path):
loaded_content = None 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_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") 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) 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": 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: else:
# Linux, Darwin # Linux, Darwin
absolute_base_path = "file://" + os.path.abspath(schemas_root_dir) + "/" 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) common_schema = json.load(f)
resolver = jsonschema.RefResolver(absolute_base_path, common_schema) 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) 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) 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) 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) testsuite_schema_v2 = json.load(f)
class JsonSchemaChecker(object): class JsonSchemaChecker(object):
@staticmethod @staticmethod
def validate_format(content, scheme): def validate_format(content, scheme):
""" check api/testcase/testsuite format if valid """check api/testcase/testsuite format if valid"""
"""
try: try:
jsonschema.validate(content, scheme, resolver=resolver) jsonschema.validate(content, scheme, resolver=resolver)
except jsonschema.exceptions.ValidationError as ex: except jsonschema.exceptions.ValidationError as ex:
@@ -57,37 +57,32 @@ class JsonSchemaChecker(object):
@staticmethod @staticmethod
def validate_api_format(content): def validate_api_format(content):
""" check api format if valid """check api format if valid"""
"""
return JsonSchemaChecker.validate_format(content, api_schema) return JsonSchemaChecker.validate_format(content, api_schema)
@staticmethod @staticmethod
def validate_testcase_v1_format(content): 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) return JsonSchemaChecker.validate_format(content, testcase_schema_v1)
@staticmethod @staticmethod
def validate_testcase_v2_format(content): 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) return JsonSchemaChecker.validate_format(content, testcase_schema_v2)
@staticmethod @staticmethod
def validate_testsuite_v1_format(content): 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) return JsonSchemaChecker.validate_format(content, testsuite_schema_v1)
@staticmethod @staticmethod
def validate_testsuite_v2_format(content): 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) return JsonSchemaChecker.validate_format(content, testsuite_schema_v2)
def is_test_path(path): 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: Args:
path (str/list/tuple): file path/directory or file path list. 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): if os.path.isfile(path):
# path is a file # path is a file
file_suffix = os.path.splitext(path)[1].lower() 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 # path is not json/yaml file
return False return False
else: else:
@@ -129,7 +124,7 @@ def is_test_path(path):
def is_test_content(data_structure): def is_test_content(data_structure):
""" check if data_structure is apis/testcases/testsuites. """check if data_structure is apis/testcases/testsuites.
Args: Args:
data_structure (dict): should include keys, apis or testcases or testsuites 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: try:
# PyYAML version >= 5.1 # PyYAML version >= 5.1
# ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation # ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation
yaml.warnings({'YAMLLoadWarning': False}) yaml.warnings({"YAMLLoadWarning": False})
except AttributeError: except AttributeError:
pass pass
def _load_yaml_file(yaml_file): def _load_yaml_file(yaml_file):
""" load yaml file and check file content format """load yaml file and check file content format"""
""" with io.open(yaml_file, "r", encoding="utf-8") as stream:
with io.open(yaml_file, 'r', encoding='utf-8') as stream:
try: try:
yaml_content = yaml.load(stream) yaml_content = yaml.load(stream)
except yaml.YAMLError as ex: except yaml.YAMLError as ex:
@@ -32,13 +31,12 @@ def _load_yaml_file(yaml_file):
def _load_json_file(json_file): def _load_json_file(json_file):
""" load json file and check file content format """load json file and check file content format"""
""" with io.open(json_file, encoding="utf-8") as data_file:
with io.open(json_file, encoding='utf-8') as data_file:
try: try:
json_content = json.load(data_file) json_content = json.load(data_file)
except exceptions.JSONDecodeError: 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) logger.log_error(err_msg)
raise exceptions.FileFormatError(err_msg) raise exceptions.FileFormatError(err_msg)
@@ -46,7 +44,7 @@ def _load_json_file(json_file):
def load_csv_file(csv_file): def load_csv_file(csv_file):
""" load csv file and check file content format """load csv file and check file content format
Args: Args:
csv_file (str): csv file path, csv file content is like below: 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 = [] 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) reader = csv.DictReader(csvfile)
for row in reader: for row in reader:
csv_content_list.append(row) csv_content_list.append(row)
@@ -93,21 +91,21 @@ def load_file(file_path):
raise exceptions.FileNotFound("{} does not exist.".format(file_path)) raise exceptions.FileNotFound("{} does not exist.".format(file_path))
file_suffix = os.path.splitext(file_path)[1].lower() file_suffix = os.path.splitext(file_path)[1].lower()
if file_suffix == '.json': if file_suffix == ".json":
return _load_json_file(file_path) return _load_json_file(file_path)
elif file_suffix in ['.yaml', '.yml']: elif file_suffix in [".yaml", ".yml"]:
return _load_yaml_file(file_path) return _load_yaml_file(file_path)
elif file_suffix == ".csv": elif file_suffix == ".csv":
return load_csv_file(file_path) return load_csv_file(file_path)
else: else:
# '' or other suffix # '' 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) logger.log_warning(err_msg)
return [] return []
def load_folder_files(folder_path, recursive=True): 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: Args:
folder_path (str): specified folder path to load folder_path (str): specified folder path to load
@@ -132,7 +130,7 @@ def load_folder_files(folder_path, recursive=True):
filenames_list = [] filenames_list = []
for filename in filenames: for filename in filenames:
if not filename.endswith(('.yml', '.yaml', '.json')): if not filename.endswith((".yml", ".yaml", ".json")):
continue continue
filenames_list.append(filename) filenames_list.append(filename)
@@ -148,7 +146,7 @@ def load_folder_files(folder_path, recursive=True):
def load_dot_env_file(dot_env_path): def load_dot_env_file(dot_env_path):
""" load .env file. """load .env file.
Args: Args:
dot_env_path (str): .env file path 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)) logger.log_info("Loading environment variables from {}".format(dot_env_path))
env_variables_mapping = {} 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: for line in fp:
# maxsplit=1 # maxsplit=1
if "=" in line: if "=" in line:
@@ -189,7 +187,7 @@ def load_dot_env_file(dot_env_path):
def load_module_functions(module): def load_module_functions(module):
""" load python module functions. """load python module functions.
Args: Args:
module: python module module: python module
@@ -213,7 +211,5 @@ def load_module_functions(module):
def load_builtin_functions(): def load_builtin_functions():
""" load builtin module functions """load builtin module functions"""
"""
return load_module_functions(builtin) return load_module_functions(builtin)

View File

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

View File

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

View File

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

View File

@@ -16,5 +16,5 @@ __all__ = [
"get_summary", "get_summary",
"stringify_summary", "stringify_summary",
"HtmlTestResult", "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.result import HtmlTestResult
from httprunner.report.html.gen_report import gen_html_report from httprunner.report.html.gen_report import gen_html_report
__all__ = [ __all__ = ["HtmlTestResult", "gen_html_report"]
"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): 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: Args:
summary (dict): test result summary data 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: if not report_template:
report_template = os.path.join( report_template = os.path.join(
os.path.abspath(os.path.dirname(__file__)), os.path.abspath(os.path.dirname(__file__)), "template.html"
"template.html"
) )
logger.log_debug("No html report template specified, use default.") logger.log_debug("No html report template specified, use default.")
else: else:
@@ -43,22 +42,22 @@ def gen_html_report(summary, report_template=None, report_dir=None, report_file=
else: else:
report_dir = report_dir or os.path.join(os.getcwd(), "reports") report_dir = report_dir or os.path.join(os.getcwd(), "reports")
# fix #826: Windows does not support file name include ":" # 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): if not os.path.isdir(report_dir):
os.makedirs(report_dir) os.makedirs(report_dir)
report_path = os.path.join(report_dir, report_file_name) 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() 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( rendered_content = Template(
template_content, template_content, extensions=["jinja2.ext.loopcontrols"]
extensions=["jinja2.ext.loopcontrols"]
).render(summary) ).render(summary)
fp_w.write(rendered_content) fp_w.write(rendered_content)
logger.log_info("Generated Html report: {}".format(report_path)) logger.log_info("Generated Html report: {}".format(report_path))
return report_path return report_path

View File

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

View File

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

View File

@@ -8,7 +8,7 @@ from httprunner.compat import basestring, bytes, json, numeric_types, JSONDecode
def dumps_json(value): def dumps_json(value):
""" dumps json value to indented string """dumps json value to indented string
Args: Args:
value (dict): raw json data value (dict): raw json data
@@ -28,7 +28,7 @@ def detect_encoding(value):
def __stringify_request(request_data): def __stringify_request(request_data):
""" stringfy HTTP request data """stringfy HTTP request data
Args: Args:
request_data (dict): HTTP request data in dict. request_data (dict): HTTP request data in dict.
@@ -84,7 +84,7 @@ def __stringify_request(request_data):
def __stringify_response(response_data): def __stringify_response(response_data):
""" stringfy HTTP response data """stringfy HTTP response data
Args: Args:
response_data (dict): response_data (dict):
@@ -124,8 +124,7 @@ def __stringify_response(response_data):
if key == "body" and "image" in response_data["content_type"]: if key == "body" and "image" in response_data["content_type"]:
# display image # display image
value = "data:{};base64,{}".format( value = "data:{};base64,{}".format(
response_data["content_type"], response_data["content_type"], b64encode(value).decode(encoding)
b64encode(value).decode(encoding)
) )
else: else:
value = escape(value.decode(encoding)) value = escape(value.decode(encoding))
@@ -143,7 +142,7 @@ def __stringify_response(response_data):
def __expand_meta_datas(meta_datas, meta_datas_expanded): def __expand_meta_datas(meta_datas, meta_datas_expanded):
""" expand meta_datas to one level """expand meta_datas to one level
Args: Args:
meta_datas (dict/list): maybe in nested format 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): 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: try:
response_time = 0 response_time = 0
for meta_data in meta_datas_expanded: for meta_data in meta_datas_expanded:
@@ -200,15 +198,14 @@ def __stringify_meta_datas(meta_datas):
def stringify_summary(summary): 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"]): for index, suite_summary in enumerate(summary["details"]):
if not suite_summary.get("name"): if not suite_summary.get("name"):
suite_summary["name"] = "testcase {}".format(index) suite_summary["name"] = "testcase {}".format(index)
for record in suite_summary.get("records"): for record in suite_summary.get("records"):
meta_datas = record['meta_datas'] meta_datas = record["meta_datas"]
__stringify_meta_datas(meta_datas) __stringify_meta_datas(meta_datas)
meta_datas_expanded = [] meta_datas_expanded = []
__expand_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 { return {
"httprunner_version": __version__, "httprunner_version": __version__,
"python_version": "{} {}".format( "python_version": "{} {}".format(
platform.python_implementation(), platform.python_implementation(), platform.python_version()
platform.python_version()
), ),
"platform": platform.platform() "platform": platform.platform(),
} }
def aggregate_stat(origin_stat, new_stat): def aggregate_stat(origin_stat, new_stat):
""" aggregate new_stat to origin_stat. """aggregate new_stat to origin_stat.
Args: Args:
origin_stat (dict): origin stat dict, will be updated with new_stat dict. 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"]) origin_stat["start_at"] = min(origin_stat["start_at"], new_stat["start_at"])
elif key == "duration": elif key == "duration":
# duration = max_end_time - min_start_time # duration = max_end_time - min_start_time
max_end_time = max(origin_stat["start_at"] + origin_stat["duration"], max_end_time = max(
new_stat["start_at"] + new_stat["duration"]) 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"]) min_start_time = min(origin_stat["start_at"], new_stat["start_at"])
origin_stat["duration"] = max_end_time - min_start_time origin_stat["duration"] = max_end_time - min_start_time
else: else:
@@ -39,7 +40,7 @@ def aggregate_stat(origin_stat, new_stat):
def get_summary(result): def get_summary(result):
""" get summary from test result """get summary from test result
Args: Args:
result (instance): HtmlTestResult() instance result (instance): HtmlTestResult() instance
@@ -58,25 +59,24 @@ def get_summary(result):
summary = { summary = {
"success": result.wasSuccessful(), "success": result.wasSuccessful(),
"stat": { "stat": {
'total': result.testsRun, "total": result.testsRun,
'failures': len(result.failures), "failures": len(result.failures),
'errors': len(result.errors), "errors": len(result.errors),
'skipped': len(result.skipped), "skipped": len(result.skipped),
'expectedFailures': len(result.expectedFailures), "expectedFailures": len(result.expectedFailures),
'unexpectedSuccesses': len(result.unexpectedSuccesses) "unexpectedSuccesses": len(result.unexpectedSuccesses),
} },
} }
summary["stat"]["successes"] = summary["stat"]["total"] \ summary["stat"]["successes"] = (
- summary["stat"]["failures"] \ summary["stat"]["total"]
- summary["stat"]["errors"] \ - summary["stat"]["failures"]
- summary["stat"]["skipped"] \ - summary["stat"]["errors"]
- summary["stat"]["expectedFailures"] \ - summary["stat"]["skipped"]
- summary["stat"]["unexpectedSuccesses"] - summary["stat"]["expectedFailures"]
- summary["stat"]["unexpectedSuccesses"]
)
summary["time"] = { summary["time"] = {"start_at": result.start_at, "duration": result.duration}
'start_at': result.start_at,
'duration': result.duration
}
summary["records"] = result.records summary["records"] = result.records
return summary return summary

View File

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

View File

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

View File

@@ -23,7 +23,7 @@ absolute_http_url_regexp = re.compile(r"^https?://", re.I)
def init_sentry_sdk(): def init_sentry_sdk():
sentry_sdk.init( sentry_sdk.init(
dsn="https://cc6dd86fbe9f4e7fbd95248cfcff114d@sentry.io/1862849", dsn="https://cc6dd86fbe9f4e7fbd95248cfcff114d@sentry.io/1862849",
release="httprunner@{}".format(__version__) release="httprunner@{}".format(__version__),
) )
with sentry_sdk.configure_scope() as scope: with sentry_sdk.configure_scope() as scope:
@@ -32,46 +32,48 @@ def init_sentry_sdk():
class GAClient(object): class GAClient(object):
version = '1' # GA API Version version = "1" # GA API Version
report_url = 'https://www.google-analytics.com/collect' report_url = "https://www.google-analytics.com/collect"
report_debug_url = 'https://www.google-analytics.com/debug/collect' # used for debug report_debug_url = (
"https://www.google-analytics.com/debug/collect" # used for debug
)
def __init__(self, tracking_id): def __init__(self, tracking_id):
self.http_client = requests.Session() self.http_client = requests.Session()
self.label = str(__version__) self.label = str(__version__)
self.common_params = { self.common_params = {
'v': self.version, "v": self.version,
'tid': tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X "tid": tracking_id, # Tracking ID / Property ID, XX-XXXXXXX-X
'cid': uuid.getnode(), # Anonymous Client ID "cid": uuid.getnode(), # Anonymous Client ID
'ua': 'HttpRunner/{}'.format(__version__), "ua": "HttpRunner/{}".format(__version__),
} }
def track_event(self, category, action, value=0): def track_event(self, category, action, value=0):
data = { data = {
't': 'event', # Event hit type = event "t": "event", # Event hit type = event
'ec': category, # Required. Event Category. "ec": category, # Required. Event Category.
'ea': action, # Required. Event Action. "ea": action, # Required. Event Action.
'el': self.label, # Optional. Event label, used as version. "el": self.label, # Optional. Event label, used as version.
'ev': value, # Optional. Event value, must be non-negative integer "ev": value, # Optional. Event value, must be non-negative integer
} }
data.update(self.common_params) data.update(self.common_params)
try: try:
self.http_client.post(self.report_url, data=data, timeout=5) self.http_client.post(self.report_url, data=data, timeout=5)
except Exception: # ProxyError, SSLError, ConnectionError except Exception: # ProxyError, SSLError, ConnectionError
pass pass
def track_user_timing(self, category, variable, duration): def track_user_timing(self, category, variable, duration):
data = { data = {
't': 'timing', # Event hit type = timing "t": "timing", # Event hit type = timing
'utc': category, # Required. user timing category. e.g. jsonLoader "utc": category, # Required. user timing category. e.g. jsonLoader
'utv': variable, # Required. timing variable. e.g. load "utv": variable, # Required. timing variable. e.g. load
'utt': duration, # Required. time took duration. "utt": duration, # Required. time took duration.
'utl': self.label, # Optional. user timing label, used as version. "utl": self.label, # Optional. user timing label, used as version.
} }
data.update(self.common_params) data.update(self.common_params)
try: try:
self.http_client.post(self.report_url, data=data, timeout=5) self.http_client.post(self.report_url, data=data, timeout=5)
except Exception: # ProxyError, SSLError, ConnectionError except Exception: # ProxyError, SSLError, ConnectionError
pass pass
@@ -79,23 +81,21 @@ ga_client = GAClient("UA-114587036-1")
def set_os_environ(variables_mapping): def set_os_environ(variables_mapping):
""" set variables mapping to os.environ """set variables mapping to os.environ"""
"""
for variable in variables_mapping: for variable in variables_mapping:
os.environ[variable] = variables_mapping[variable] os.environ[variable] = variables_mapping[variable]
logger.log_debug("Set OS environment variable: {}".format(variable)) logger.log_debug("Set OS environment variable: {}".format(variable))
def unset_os_environ(variables_mapping): def unset_os_environ(variables_mapping):
""" set variables mapping to os.environ """set variables mapping to os.environ"""
"""
for variable in variables_mapping: for variable in variables_mapping:
os.environ.pop(variable) os.environ.pop(variable)
logger.log_debug("Unset OS environment variable: {}".format(variable)) logger.log_debug("Unset OS environment variable: {}".format(variable))
def get_os_environ(variable_name): def get_os_environ(variable_name):
""" get value of environment variable. """get value of environment variable.
Args: Args:
variable_name(str): variable name variable_name(str): variable name
@@ -114,7 +114,7 @@ def get_os_environ(variable_name):
def build_url(base_url, path): 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): if absolute_http_url_regexp.match(path):
return path return path
elif base_url: elif base_url:
@@ -123,8 +123,8 @@ def build_url(base_url, path):
raise ParamsError("base url missed!") raise ParamsError("base url missed!")
def query_json(json_content, query, delimiter='.'): def query_json(json_content, query, delimiter="."):
""" Do an xpath-like query with json_content. """Do an xpath-like query with json_content.
Args: Args:
json_content (dict/list/string): content to be queried. json_content (dict/list/string): content to be queried.
@@ -158,7 +158,7 @@ def query_json(json_content, query, delimiter='.'):
""" """
raise_flag = False raise_flag = False
response_body = u"response body: {}\n".format(json_content) response_body = "response body: {}\n".format(json_content)
try: try:
for key in query.split(delimiter): for key in query.split(delimiter):
if isinstance(json_content, (list, basestring)): if isinstance(json_content, (list, basestring)):
@@ -167,13 +167,16 @@ def query_json(json_content, query, delimiter='.'):
json_content = json_content[key] json_content = json_content[key]
else: else:
logger.log_error( logger.log_error(
"invalid type value: {}({})".format(json_content, type(json_content))) "invalid type value: {}({})".format(
json_content, type(json_content)
)
)
raise_flag = True raise_flag = True
except (KeyError, ValueError, IndexError): except (KeyError, ValueError, IndexError):
raise_flag = True raise_flag = True
if raise_flag: if raise_flag:
err_msg = u"Failed to extract! => {}\n".format(query) err_msg = "Failed to extract! => {}\n".format(query)
err_msg += response_body err_msg += response_body
logger.log_error(err_msg) logger.log_error(err_msg)
raise exceptions.ExtractFailure(err_msg) raise exceptions.ExtractFailure(err_msg)
@@ -182,7 +185,7 @@ def query_json(json_content, query, delimiter='.'):
def lower_dict_keys(origin_dict): def lower_dict_keys(origin_dict):
""" convert keys in dict to lower case """convert keys in dict to lower case
Args: Args:
origin_dict (dict): mapping data structure 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): if not origin_dict or not isinstance(origin_dict, dict):
return origin_dict return origin_dict
return { return {key.lower(): value for key, value in origin_dict.items()}
key.lower(): value
for key, value in origin_dict.items()
}
def lower_test_dict_keys(test_dict): def lower_test_dict_keys(test_dict):
""" convert keys in test_dict to lower case, convertion will occur in two places: """convert keys in test_dict to lower case, convertion will occur in two places:
1, all keys in test_dict; 1, all keys in test_dict;
2, all keys in test_dict["request"] 2, all keys in test_dict["request"]
""" """
# convert keys in test_dict # convert keys in test_dict
test_dict = lower_dict_keys(test_dict) test_dict = lower_dict_keys(test_dict)
@@ -235,7 +235,7 @@ def lower_test_dict_keys(test_dict):
def deepcopy_dict(data): def deepcopy_dict(data):
""" deepcopy dict data, ignore file object (_io.BufferedReader) """deepcopy dict data, ignore file object (_io.BufferedReader)
Args: Args:
data (dict): dict data structure data (dict): dict data structure
@@ -271,7 +271,7 @@ def deepcopy_dict(data):
def ensure_mapping_format(variables): def ensure_mapping_format(variables):
""" ensure variables are in mapping format. """ensure variables are in mapping format.
Args: Args:
variables (list/dict): original variables variables (list/dict): original variables
@@ -306,7 +306,7 @@ def ensure_mapping_format(variables):
def extend_variables(raw_variables, override_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. override_variables will merge and override raw_variables.
Args: Args:
@@ -343,7 +343,7 @@ def extend_variables(raw_variables, override_variables):
def get_testcase_io(testcase): def get_testcase_io(testcase):
""" get and print testcase input(variables) and output(export). """get and print testcase input(variables) and output(export).
Args: Args:
testcase (unittest.suite.TestSuite): corresponding to one YAML/JSON file, it has been set two attributes: 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 test_runner = testcase.runner
variables = testcase.config.get("variables", {}) variables = testcase.config.get("variables", {})
output_list = testcase.config.get("export") \ output_list = testcase.config.get("export") or testcase.config.get("output", [])
or testcase.config.get("output", [])
export_mapping = test_runner.export_variables(output_list) export_mapping = test_runner.export_variables(output_list)
return { return {"in": variables, "out": export_mapping}
"in": variables,
"out": export_mapping
}
def print_info(info_mapping): def print_info(info_mapping):
""" print info in mapping. """print info in mapping.
Args: Args:
info_mapping (dict): input(variables) or output mapping. info_mapping (dict): input(variables) or output mapping.
@@ -417,10 +413,11 @@ def print_info(info_mapping):
def create_scaffold(project_name): def create_scaffold(project_name):
""" create scaffold with specified project name. """create scaffold with specified project name."""
"""
if os.path.isdir(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 return
ga_client.track_event("Scaffold", "startproject") ga_client.track_event("Scaffold", "startproject")
@@ -433,7 +430,7 @@ def create_scaffold(project_name):
logger.color_print(msg, "BLUE") logger.color_print(msg, "BLUE")
def create_file(path, file_content=""): def create_file(path, file_content=""):
with open(path, 'w') as f: with open(path, "w") as f:
f.write(file_content) f.write(file_content)
msg = "created file: {}".format(path) msg = "created file: {}".format(path)
logger.color_print(msg, "BLUE") logger.color_print(msg, "BLUE")
@@ -498,24 +495,16 @@ testcases:
variables: variables:
device_sn: $device_sn device_sn: $device_sn
""" """
ignore_content = "\n".join([ ignore_content = "\n".join(
".env", [".env", "reports/*", "__pycache__/*", "*.pyc", ".python-version", "logs/*"]
"reports/*", )
"__pycache__/*",
"*.pyc",
".python-version",
"logs/*"
])
demo_debugtalk_content = """ demo_debugtalk_content = """
import time import time
def sleep(n_secs): def sleep(n_secs):
time.sleep(n_secs) time.sleep(n_secs)
""" """
demo_env_content = "\n".join([ demo_env_content = "\n".join(["USERNAME=leolee", "PASSWORD=123456"])
"USERNAME=leolee",
"PASSWORD=123456"
])
create_folder(project_name) create_folder(project_name)
create_folder(os.path.join(project_name, "api")) 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, "testsuites"))
create_folder(os.path.join(project_name, "reports")) 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, "api", "demo_api.yml"), demo_api_content)
create_file(os.path.join(project_name, "testcases", "demo_testcase.yml"), demo_testcase_content) create_file(
create_file(os.path.join(project_name, "testsuites", "demo_testsuite.yml"), demo_testsuite_content) 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, "debugtalk.py"), demo_debugtalk_content)
create_file(os.path.join(project_name, ".env"), demo_env_content) create_file(os.path.join(project_name, ".env"), demo_env_content)
create_file(os.path.join(project_name, ".gitignore"), ignore_content) create_file(os.path.join(project_name, ".gitignore"), ignore_content)
def gen_cartesian_product(*args): def gen_cartesian_product(*args):
""" generate cartesian product for lists """generate cartesian product for lists
Args: Args:
args (list of list): lists to be generated with cartesian product 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): def prettify_json_file(file_list):
""" prettify JSON testcase format """prettify JSON testcase format"""
"""
for json_file in set(file_list): for json_file in set(file_list):
if not json_file.endswith(".json"): 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 continue
logger.color_print("Start to prettify JSON file: {}".format(json_file), "GREEN") 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)) file_name, file_suffix = os.path.splitext(os.path.basename(json_file))
outfile = os.path.join(dir_path, "{}.pretty.json".format(file_name)) 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: try:
obj = json.load(stream) obj = json.load(stream)
except ValueError as e: except ValueError as e:
raise SystemExit(e) raise SystemExit(e)
with io.open(outfile, 'w', encoding='utf-8') as out: with io.open(outfile, "w", encoding="utf-8") as out:
json.dump(obj, out, indent=4, separators=(',', ': ')) json.dump(obj, out, indent=4, separators=(",", ": "))
out.write('\n') out.write("\n")
print("success: {}".format(outfile)) print("success: {}".format(outfile))
def omit_long_data(body, omit_len=512): def omit_long_data(body, omit_len=512):
""" omit too long str/bytes """omit too long str/bytes"""
"""
if not isinstance(body, basestring): if not isinstance(body, basestring):
return body return body
@@ -618,8 +613,8 @@ def omit_long_data(body, omit_len=512):
def dump_json_file(json_data, json_file_abs_path): def dump_json_file(json_data, json_file_abs_path):
""" dump json data to file """dump json data to file"""
"""
class PythonObjectEncoder(json.JSONEncoder): class PythonObjectEncoder(json.JSONEncoder):
def default(self, obj): def default(self, obj):
try: try:
@@ -632,26 +627,28 @@ def dump_json_file(json_data, json_file_abs_path):
os.makedirs(file_foder_path) os.makedirs(file_foder_path)
try: 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: if is_py2:
outfile.write( outfile.write(
unicode(json.dumps( unicode(
json_data, json.dumps(
indent=4, json_data,
separators=(',', ':'), indent=4,
encoding="utf8", separators=(",", ":"),
ensure_ascii=False, encoding="utf8",
cls=PythonObjectEncoder ensure_ascii=False,
)) cls=PythonObjectEncoder,
)
)
) )
else: else:
json.dump( json.dump(
json_data, json_data,
outfile, outfile,
indent=4, indent=4,
separators=(',', ':'), separators=(",", ":"),
ensure_ascii=False, ensure_ascii=False,
cls=PythonObjectEncoder cls=PythonObjectEncoder,
) )
msg = "dump file: {}".format(json_file_abs_path) 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): 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() pwd_dir_path = project_mapping.get("PWD") or os.getcwd()
test_path = project_mapping.get("test_path") 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 # both test_path and pwd_dir_path are absolute path
logs_dir_path = os.path.join(pwd_dir_path, "logs") 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): if os.path.isdir(test_path):
file_foder_path = os.path.join(logs_dir_path, test_path_relative_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): 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. the dumped file is located in PWD/logs folder.
Args: Args:
@@ -711,7 +707,11 @@ def get_python2_retire_msg():
left_days = (retire_day - today).days left_days = (retire_day - today).days
if left_days > 0: 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: else:
retire_msg = "Python 2 has been retired, you should move to Python 3." 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): def __init__(self, session_context, resp_obj):
""" initialize a Validator for each teststep (API request) """initialize a Validator for each teststep (API request)
Args: Args:
session_context: HttpRunner session context session_context: HttpRunner session context
@@ -27,7 +27,7 @@ class Validator(object):
self.validation_results = {} self.validation_results = {}
def __eval_validator_check(self, check_item): def __eval_validator_check(self, check_item):
""" evaluate check item in validator. """evaluate check item in validator.
Args: Args:
check_item: check_item should only be the following 5 formats: 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]*" 5, regex string, e.g. "LB[\d]*(.*)RB[\d]*"
""" """
if isinstance(check_item, (dict, list)) \ if isinstance(check_item, (dict, list)) or isinstance(
or isinstance(check_item, parser.LazyString): check_item, parser.LazyString
):
# format 1/2/3 # format 1/2/3
check_value = self.session_context.eval_content(check_item) check_value = self.session_context.eval_content(check_item)
else: else:
@@ -49,7 +50,7 @@ class Validator(object):
return check_value return check_value
def __eval_validator_expect(self, expect_item): def __eval_validator_expect(self, expect_item):
""" evaluate expect item in validator. """evaluate expect item in validator.
Args: Args:
expect_item: expect_item should only be in 2 types: expect_item: expect_item should only be in 2 types:
@@ -61,12 +62,11 @@ class Validator(object):
return expect_value return expect_value
def validate_script(self, script): def validate_script(self, script):
""" make validation with python script """make validation with python script"""
"""
result = { result = {
"validate_script": "<br/>".join(script), "validate_script": "<br/>".join(script),
"check_result": "pass", "check_result": "pass",
"output": "" "output": "",
} }
script = "\n ".join(script) script = "\n ".join(script)
@@ -75,12 +75,14 @@ class Validator(object):
def run_validate_script(): def run_validate_script():
{} {}
""".format(script) """.format(
script
)
variables = { variables = {
"status_code": self.resp_obj.status_code, "status_code": self.resp_obj.status_code,
"response_json": self.resp_obj.json, "response_json": self.resp_obj.json,
"response": self.resp_obj "response": self.resp_obj,
} }
variables.update(self.session_context.test_variables_mapping) variables.update(self.session_context.test_variables_mapping)
variables.update(globals()) variables.update(globals())
@@ -90,11 +92,13 @@ def run_validate_script():
except SyntaxError as ex: except SyntaxError as ex:
logger.log_warning("SyntaxError in python validate script: {}".format(ex)) logger.log_warning("SyntaxError in python validate script: {}".format(ex))
result["check_result"] = "fail" result["check_result"] = "fail"
result["output"] = "<br/>".join([ result["output"] = "<br/>".join(
"ErrorMessage: {}".format(ex.msg), [
"ErrorLine: {}".format(ex.lineno), "ErrorMessage: {}".format(ex.msg),
"ErrorText: {}".format(ex.text) "ErrorLine: {}".format(ex.lineno),
]) "ErrorText: {}".format(ex.text),
]
)
return result return result
try: try:
@@ -117,16 +121,17 @@ def run_validate_script():
else: else:
line_no = "N/A" line_no = "N/A"
result["output"] = "<br/>".join([ result["output"] = "<br/>".join(
"ErrorType: {}".format(_type.__name__), [
"ErrorLine: {}".format(line_no) "ErrorType: {}".format(_type.__name__),
]) "ErrorLine: {}".format(line_no),
]
)
return result return result
def validate(self, validators): def validate(self, validators):
""" make validation with comparators """make validation with comparators"""
"""
self.validation_results = {} self.validation_results = {}
if not validators: if not validators:
return return
@@ -154,7 +159,8 @@ def run_validate_script():
# validator should be LazyFunction object # validator should be LazyFunction object
if not isinstance(validator, parser.LazyFunction): if not isinstance(validator, parser.LazyFunction):
raise exceptions.ValidationFailure( raise exceptions.ValidationFailure(
"validator should be parsed first: {}".format(validators)) "validator should be parsed first: {}".format(validators)
)
# evaluate validator args with context variable mapping. # evaluate validator args with context variable mapping.
validator_args = validator.get_args() validator_args = validator.get_args()
@@ -169,13 +175,10 @@ def run_validate_script():
"check": check_item, "check": check_item,
"check_value": check_value, "check_value": check_value,
"expect": expect_item, "expect": expect_item,
"expect_value": expect_value "expect_value": expect_value,
} }
validate_msg = "\nvalidate: {} {} {}({})".format( validate_msg = "\nvalidate: {} {} {}({})".format(
check_item, check_item, comparator, expect_value, type(expect_value).__name__
comparator,
expect_value,
type(expect_value).__name__
) )
try: try:
@@ -192,7 +195,7 @@ def run_validate_script():
type(check_value).__name__, type(check_value).__name__,
comparator, comparator,
expect_value, expect_value,
type(expect_value).__name__ type(expect_value).__name__,
) )
logger.log_error(validate_msg) logger.log_error(validate_msg)
failures.append(validate_msg) failures.append(validate_msg)

View File

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

View File

@@ -19,18 +19,13 @@ def run_httpbin():
class ApiServerUnittest(unittest.TestCase): 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 @classmethod
def setUpClass(cls): def setUpClass(cls):
cls.host = "http://127.0.0.1:5000" cls.host = "http://127.0.0.1:5000"
cls.flask_process = multiprocessing.Process( cls.flask_process = multiprocessing.Process(target=run_flask)
target=run_flask cls.httpbin_process = multiprocessing.Process(target=run_httpbin)
)
cls.httpbin_process = multiprocessing.Process(
target=run_httpbin
)
cls.flask_process.start() cls.flask_process.start()
cls.httpbin_process.start() cls.httpbin_process.start()
time.sleep(1) time.sleep(1)
@@ -44,15 +39,13 @@ class ApiServerUnittest(unittest.TestCase):
def get_token(self, user_agent, device_sn, os_platform, app_version): def get_token(self, user_agent, device_sn, os_platform, app_version):
url = "%s/api/get-token" % self.host url = "%s/api/get-token" % self.host
headers = { headers = {
'Content-Type': 'application/json', "Content-Type": "application/json",
'User-Agent': user_agent, "User-Agent": user_agent,
'device_sn': device_sn, "device_sn": device_sn,
'os_platform': os_platform, "os_platform": os_platform,
'app_version': app_version "app_version": app_version,
}
data = {
'sign': get_sign(device_sn, os_platform, app_version)
} }
data = {"sign": get_sign(device_sn, os_platform, app_version)}
resp = self.api_client.post(url, json=data, headers=headers) resp = self.api_client.post(url, json=data, headers=headers)
resp_json = resp.json() resp_json = resp.json()
@@ -62,14 +55,11 @@ class ApiServerUnittest(unittest.TestCase):
return resp_json["token"] return resp_json["token"]
def get_authenticated_headers(self): def get_authenticated_headers(self):
user_agent = 'iOS/10.3' user_agent = "iOS/10.3"
device_sn = gen_random_string(15) device_sn = gen_random_string(15)
os_platform = 'ios' os_platform = "ios"
app_version = '2.8.6' app_version = "2.8.6"
token = self.get_token(user_agent, device_sn, os_platform, app_version) token = self.get_token(user_agent, device_sn, os_platform, app_version)
headers = { headers = {"device_sn": device_sn, "token": token}
'device_sn': device_sn,
'token': token
}
return headers return headers

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,3 @@
import os import os
import unittest import unittest
@@ -7,14 +6,15 @@ from httprunner.loader import buildup
class TestModuleLoader(unittest.TestCase): class TestModuleLoader(unittest.TestCase):
def test_filter_module_functions(self): def test_filter_module_functions(self):
module_functions = buildup.load_module_functions(buildup) module_functions = buildup.load_module_functions(buildup)
self.assertIn("load_module_functions", module_functions) self.assertIn("load_module_functions", module_functions)
self.assertNotIn("is_py3", module_functions) self.assertNotIn("is_py3", module_functions)
def test_load_debugtalk_module(self): 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"]) self.assertNotIn("alter_response", project_mapping["functions"])
project_mapping = buildup.load_project_data(os.path.join(os.getcwd(), "tests")) 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_mapping = buildup.load_project_data("tests/data/demo_testcase.yml")
project_working_directory = project_mapping["PWD"] project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"] debugtalk_functions = project_mapping["functions"]
self.assertEqual( self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
project_working_directory,
os.path.join(os.getcwd(), "tests")
)
self.assertIn("gen_md5", debugtalk_functions) self.assertIn("gen_md5", debugtalk_functions)
project_mapping = buildup.load_project_data("tests/base.py") project_mapping = buildup.load_project_data("tests/base.py")
project_working_directory = project_mapping["PWD"] project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"] debugtalk_functions = project_mapping["functions"]
self.assertEqual( self.assertEqual(project_working_directory, os.path.join(os.getcwd(), "tests"))
project_working_directory,
os.path.join(os.getcwd(), "tests")
)
self.assertIn("gen_md5", debugtalk_functions) self.assertIn("gen_md5", debugtalk_functions)
project_mapping = buildup.load_project_data("httprunner/__init__.py") project_mapping = buildup.load_project_data("httprunner/__init__.py")
project_working_directory = project_mapping["PWD"] project_working_directory = project_mapping["PWD"]
debugtalk_functions = project_mapping["functions"] debugtalk_functions = project_mapping["functions"]
self.assertEqual( self.assertEqual(project_working_directory, os.getcwd())
project_working_directory,
os.getcwd()
)
self.assertEqual(debugtalk_functions, {}) self.assertEqual(debugtalk_functions, {})
class TestSuiteLoader(unittest.TestCase): class TestSuiteLoader(unittest.TestCase):
@classmethod @classmethod
def setUpClass(cls): 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 cls.tests_def_mapping = buildup.tests_def_mapping
def test_load_teststep_api(self): def test_load_teststep_api(self):
raw_test = { raw_test = {
"name": "create user (override).", "name": "create user (override).",
"api": "api/create_user.yml", "api": "api/create_user.yml",
"variables": [ "variables": [{"uid": "999"}],
{"uid": "999"}
]
} }
teststep = buildup.load_teststep(raw_test) teststep = buildup.load_teststep(raw_test)
self.assertEqual( self.assertEqual("create user (override).", teststep["name"])
"create user (override).",
teststep["name"]
)
self.assertIn("api_def", teststep) self.assertIn("api_def", teststep)
api_def = teststep["api_def"] api_def = teststep["api_def"]
self.assertEqual(api_def["name"], "create user") self.assertEqual(api_def["name"], "create user")
@@ -82,15 +69,10 @@ class TestSuiteLoader(unittest.TestCase):
raw_test = { raw_test = {
"name": "setup and reset all (override).", "name": "setup and reset all (override).",
"testcase": "testcases/setup.yml", "testcase": "testcases/setup.yml",
"variables": [ "variables": [{"device_sn": "$device_sn"}],
{"device_sn": "$device_sn"}
]
} }
testcase = buildup.load_teststep(raw_test) testcase = buildup.load_teststep(raw_test)
self.assertEqual( self.assertEqual("setup and reset all (override).", testcase["name"])
"setup and reset all (override).",
testcase["name"]
)
tests = testcase["testcase_def"]["teststeps"] tests = testcase["testcase_def"]["teststeps"]
self.assertEqual(len(tests), 2) self.assertEqual(len(tests), 2)
self.assertEqual(tests[0]["name"], "get token (setup)") self.assertEqual(tests[0]["name"], "get token (setup)")
@@ -106,7 +88,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testcase(self): def test_load_test_file_testcase(self):
for loaded_content in [ for loaded_content in [
buildup.load_test_file("tests/testcases/setup.yml"), 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.assertEqual(loaded_content["type"], "testcase")
self.assertIn("path", loaded_content) self.assertIn("path", loaded_content)
@@ -118,7 +100,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testcase_v2(self): def test_load_test_file_testcase_v2(self):
for loaded_content in [ for loaded_content in [
buildup.load_test_file("tests/testcases/setup.v2.yml"), 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.assertEqual(loaded_content["type"], "testcase")
self.assertIn("path", loaded_content) self.assertIn("path", loaded_content)
@@ -130,38 +112,45 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_test_file_testsuite(self): def test_load_test_file_testsuite(self):
for loaded_content in [ for loaded_content in [
buildup.load_test_file("tests/testsuites/create_users.yml"), 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") self.assertEqual(loaded_content["type"], "testsuite")
testcases = loaded_content["testcases"] testcases = loaded_content["testcases"]
self.assertEqual(len(testcases), 2) self.assertEqual(len(testcases), 2)
self.assertIn('create user 1000 and check result.', testcases) self.assertIn("create user 1000 and check result.", testcases)
self.assertIn('testcase_def', testcases["create user 1000 and check result."]) self.assertIn(
"testcase_def", testcases["create user 1000 and check result."]
)
self.assertEqual( self.assertEqual(
testcases["create user 1000 and check result."]["testcase_def"]["config"]["name"], testcases["create user 1000 and check result."]["testcase_def"][
"create user and check result." "config"
]["name"],
"create user and check result.",
) )
def test_load_test_file_testsuite_v2(self): def test_load_test_file_testsuite_v2(self):
for loaded_content in [ for loaded_content in [
buildup.load_test_file("tests/testsuites/create_users.v2.yml"), 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") self.assertEqual(loaded_content["type"], "testsuite")
testcases = loaded_content["testcases"] testcases = loaded_content["testcases"]
self.assertEqual(len(testcases), 2) self.assertEqual(len(testcases), 2)
self.assertIn('create user 1000 and check result.', testcases) self.assertIn("create user 1000 and check result.", testcases)
self.assertIn('testcase_def', testcases["create user 1000 and check result."]) self.assertIn(
"testcase_def", testcases["create user 1000 and check result."]
)
self.assertEqual( self.assertEqual(
testcases["create user 1000 and check result."]["testcase_def"]["config"]["name"], testcases["create user 1000 and check result."]["testcase_def"][
"create user and check result." "config"
]["name"],
"create user and check result.",
) )
def test_load_tests_api_file(self): def test_load_tests_api_file(self):
path = os.path.join( path = os.path.join(os.getcwd(), "tests/api/create_user.yml")
os.getcwd(), 'tests/api/create_user.yml')
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"] project_mapping = tests_mapping["project_mapping"]
api_list = tests_mapping["apis"] api_list = tests_mapping["apis"]
@@ -170,8 +159,7 @@ class TestSuiteLoader(unittest.TestCase):
def test_load_tests_testcase_file(self): def test_load_tests_testcase_file(self):
# absolute file path # absolute file path
path = os.path.join( path = os.path.join(os.getcwd(), "tests/data/demo_testcase_hardcode.json")
os.getcwd(), 'tests/data/demo_testcase_hardcode.json')
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"] project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"] testcases_list = tests_mapping["testcases"]
@@ -180,7 +168,7 @@ class TestSuiteLoader(unittest.TestCase):
self.assertIn("get_sign", project_mapping["functions"]) self.assertIn("get_sign", project_mapping["functions"])
# relative file path # relative file path
path = 'tests/data/demo_testcase_hardcode.yml' path = "tests/data/demo_testcase_hardcode.yml"
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"] project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"] testcases_list = tests_mapping["testcases"]
@@ -189,91 +177,75 @@ class TestSuiteLoader(unittest.TestCase):
self.assertIn("get_sign", project_mapping["functions"]) self.assertIn("get_sign", project_mapping["functions"])
def test_load_tests_testcase_file_2(self): def test_load_tests_testcase_file_2(self):
testcase_file_path = os.path.join( testcase_file_path = os.path.join(os.getcwd(), "tests/data/demo_testcase.yml")
os.getcwd(), 'tests/data/demo_testcase.yml')
tests_mapping = loader.load_cases(testcase_file_path) tests_mapping = loader.load_cases(testcase_file_path)
testcases = tests_mapping["testcases"] testcases = tests_mapping["testcases"]
self.assertIsInstance(testcases, list) self.assertIsInstance(testcases, list)
self.assertEqual(testcases[0]["config"]["name"], '123t$var_a') self.assertEqual(testcases[0]["config"]["name"], "123t$var_a")
self.assertIn( self.assertIn("sum_two", tests_mapping["project_mapping"]["functions"])
"sum_two", self.assertEqual(
tests_mapping["project_mapping"]["functions"] testcases[0]["config"]["variables"]["var_c"], "${sum_two($var_a, $var_b)}"
) )
self.assertEqual( self.assertEqual(
testcases[0]["config"]["variables"]["var_c"], testcases[0]["config"]["variables"]["PROJECT_KEY"], "${ENV(PROJECT_KEY)}"
"${sum_two($var_a, $var_b)}"
)
self.assertEqual(
testcases[0]["config"]["variables"]["PROJECT_KEY"],
"${ENV(PROJECT_KEY)}"
) )
def test_load_tests_testcase_file_with_api_ref(self): def test_load_tests_testcase_file_with_api_ref(self):
path = os.path.join( path = os.path.join(os.getcwd(), "tests/data/demo_testcase_layer.yml")
os.getcwd(), 'tests/data/demo_testcase_layer.yml')
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"] project_mapping = tests_mapping["project_mapping"]
testcases_list = tests_mapping["testcases"] 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("gen_md5", project_mapping["functions"])
self.assertIn("base_url", testcases_list[0]["config"]) self.assertIn("base_url", testcases_list[0]["config"])
test_dict0 = testcases_list[0]["teststeps"][0] test_dict0 = testcases_list[0]["teststeps"][0]
self.assertEqual( self.assertEqual("get token with $user_agent, $app_version", test_dict0["name"])
"get token with $user_agent, $app_version",
test_dict0["name"]
)
self.assertIn("/api/get-token", test_dict0["api_def"]["request"]["url"]) self.assertIn("/api/get-token", test_dict0["api_def"]["request"]["url"])
self.assertIn( self.assertIn({"eq": ["status_code", 200]}, test_dict0["validate"])
{'eq': ['status_code', 200]},
test_dict0["validate"]
)
def test_load_tests_testsuite_file_with_testcase_ref(self): def test_load_tests_testsuite_file_with_testcase_ref(self):
path = os.path.join( path = os.path.join(os.getcwd(), "tests/testsuites/create_users.yml")
os.getcwd(), 'tests/testsuites/create_users.yml')
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
project_mapping = tests_mapping["project_mapping"] project_mapping = tests_mapping["project_mapping"]
testsuites_list = tests_mapping["testsuites"] testsuites_list = tests_mapping["testsuites"]
self.assertEqual("create users with uid", testsuites_list[0]["config"]["name"])
self.assertEqual( self.assertEqual(
"create users with uid", "${gen_random_string(15)}",
testsuites_list[0]["config"]["name"] testsuites_list[0]["config"]["variables"]["device_sn"],
)
self.assertEqual(
'${gen_random_string(15)}',
testsuites_list[0]["config"]["variables"]['device_sn']
) )
self.assertIn( self.assertIn(
"create user 1000 and check result.", "create user 1000 and check result.", testsuites_list[0]["testcases"]
testsuites_list[0]["testcases"]
) )
self.assertEqual( self.assertEqual(
testsuites_list[0]["testcases"]["create user 1000 and check result."]["testcase_def"]["config"]["name"], testsuites_list[0]["testcases"]["create user 1000 and check result."][
"create user and check result." "testcase_def"
]["config"]["name"],
"create user and check result.",
) )
def test_load_tests_folder_path(self): def test_load_tests_folder_path(self):
# absolute folder path # 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) tests_mapping = loader.load_cases(path)
testcase_list_1 = tests_mapping["testcases"] testcase_list_1 = tests_mapping["testcases"]
self.assertGreater(len(testcase_list_1), 4) self.assertGreater(len(testcase_list_1), 4)
# relative folder path # relative folder path
path = 'tests/data/' path = "tests/data/"
tests_mapping = loader.load_cases(path) tests_mapping = loader.load_cases(path)
testcase_list_2 = tests_mapping["testcases"] testcase_list_2 = tests_mapping["testcases"]
self.assertEqual(len(testcase_list_1), len(testcase_list_2)) self.assertEqual(len(testcase_list_1), len(testcase_list_2))
def test_load_tests_path_not_exist(self): def test_load_tests_path_not_exist(self):
# absolute folder path # 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): with self.assertRaises(exceptions.FileNotFound):
loader.load_cases(path) loader.load_cases(path)
# relative folder path # relative folder path
path = 'tests/data_not_exist' path = "tests/data_not_exist"
with self.assertRaises(exceptions.FileNotFound): with self.assertRaises(exceptions.FileNotFound):
loader.load_cases(path) loader.load_cases(path)
@@ -281,5 +253,11 @@ class TestSuiteLoader(unittest.TestCase):
buildup.load_project_data(os.path.join(os.getcwd(), "tests")) buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
self.assertIn("gen_md5", self.project_mapping["functions"]) self.assertIn("gen_md5", self.project_mapping["functions"])
self.assertEqual(self.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH") 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.assertEqual(self.project_mapping["test_path"], os.path.abspath(os.path.dirname(os.path.dirname(__file__)))) 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): class TestLoaderCheck(unittest.TestCase):
def test_is_testcases(self): def test_is_testcases(self):
data_structure = "path/to/file" data_structure = "path/to/file"
self.assertFalse(check.is_test_content(data_structure)) self.assertFalse(check.is_test_content(data_structure))
@@ -12,11 +11,7 @@ class TestLoaderCheck(unittest.TestCase):
self.assertFalse(check.is_test_content(data_structure)) self.assertFalse(check.is_test_content(data_structure))
data_structure = { data_structure = {
"project_mapping": { "project_mapping": {"PWD": "XXXXX", "functions": {}, "env": {}},
"PWD": "XXXXX",
"functions": {},
"env": {}
},
"testcases": [ "testcases": [
{ # testcase data structure { # testcase data structure
"config": { "config": {
@@ -27,19 +22,19 @@ class TestLoaderCheck(unittest.TestCase):
"teststeps": [ "teststeps": [
# test data structure # test data structure
{ {
'name': 'test step desc1', "name": "test step desc1",
'variables': [], # optional "variables": [], # optional
'extract': {}, # optional "extract": {}, # optional
'validate': [], "validate": [],
'request': { "request": {
"method": "GET", "method": "GET",
"url": "https://docs.httprunner.org" "url": "https://docs.httprunner.org",
} },
}, },
# test_dict2 # another test dict # test_dict2 # another test dict
] ],
}, },
# testcase_dict_2 # another testcase dict # testcase_dict_2 # another testcase dict
] ],
} }
self.assertTrue(check.is_test_content(data_structure)) 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): class TestFileLoader(unittest.TestCase):
def test_load_yaml_file_file_format_error(self): def test_load_yaml_file_file_format_error(self):
yaml_tmp_file = "tests/data/tmp.yml" yaml_tmp_file = "tests/data/tmp.yml"
# create empty yaml file # create empty yaml file
with open(yaml_tmp_file, 'w') as f: with open(yaml_tmp_file, "w") as f:
f.write("") f.write("")
with self.assertRaises(exceptions.FileFormatError): with self.assertRaises(exceptions.FileFormatError):
@@ -20,7 +19,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(yaml_tmp_file) os.remove(yaml_tmp_file)
# create invalid format yaml 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") f.write("abc")
with self.assertRaises(exceptions.FileFormatError): with self.assertRaises(exceptions.FileFormatError):
@@ -31,7 +30,7 @@ class TestFileLoader(unittest.TestCase):
def test_load_json_file_file_format_error(self): def test_load_json_file_file_format_error(self):
json_tmp_file = "tests/data/tmp.json" json_tmp_file = "tests/data/tmp.json"
# create empty file # create empty file
with open(json_tmp_file, 'w') as f: with open(json_tmp_file, "w") as f:
f.write("") f.write("")
with self.assertRaises(exceptions.FileFormatError): with self.assertRaises(exceptions.FileFormatError):
@@ -40,7 +39,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file) os.remove(json_tmp_file)
# create empty json file # create empty json file
with open(json_tmp_file, 'w') as f: with open(json_tmp_file, "w") as f:
f.write("{}") f.write("{}")
with self.assertRaises(exceptions.FileFormatError): with self.assertRaises(exceptions.FileFormatError):
@@ -49,7 +48,7 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file) os.remove(json_tmp_file)
# create invalid format json 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") f.write("abc")
with self.assertRaises(exceptions.FileFormatError): with self.assertRaises(exceptions.FileFormatError):
@@ -58,62 +57,62 @@ class TestFileLoader(unittest.TestCase):
os.remove(json_tmp_file) os.remove(json_tmp_file)
def test_load_testcases_bad_filepath(self): 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): with self.assertRaises(exceptions.FileNotFound):
load.load_file(testcase_file_path) load.load_file(testcase_file_path)
def test_load_json_testcases(self): def test_load_json_testcases(self):
testcase_file_path = os.path.join( 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) testcases = load.load_file(testcase_file_path)
self.assertEqual(len(testcases), 3) self.assertEqual(len(testcases), 3)
test = testcases[0]["test"] test = testcases[0]["test"]
self.assertIn('name', test) self.assertIn("name", test)
self.assertIn('request', test) self.assertIn("request", test)
self.assertIn('url', test['request']) self.assertIn("url", test["request"])
self.assertIn('method', test['request']) self.assertIn("method", test["request"])
def test_load_yaml_testcases(self): def test_load_yaml_testcases(self):
testcase_file_path = os.path.join( 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) testcases = load.load_file(testcase_file_path)
self.assertEqual(len(testcases), 3) self.assertEqual(len(testcases), 3)
test = testcases[0]["test"] test = testcases[0]["test"]
self.assertIn('name', test) self.assertIn("name", test)
self.assertIn('request', test) self.assertIn("request", test)
self.assertIn('url', test['request']) self.assertIn("url", test["request"])
self.assertIn('method', test['request']) self.assertIn("method", test["request"])
def test_load_csv_file_one_parameter(self): def test_load_csv_file_one_parameter(self):
csv_file_path = os.path.join( csv_file_path = os.path.join(os.getcwd(), "tests/data/user_agent.csv")
os.getcwd(), 'tests/data/user_agent.csv')
csv_content = load.load_file(csv_file_path) csv_content = load.load_file(csv_file_path)
self.assertEqual( self.assertEqual(
csv_content, csv_content,
[ [
{'user_agent': 'iOS/10.1'}, {"user_agent": "iOS/10.1"},
{'user_agent': 'iOS/10.2'}, {"user_agent": "iOS/10.2"},
{'user_agent': 'iOS/10.3'} {"user_agent": "iOS/10.3"},
] ],
) )
def test_load_csv_file_multiple_parameters(self): def test_load_csv_file_multiple_parameters(self):
csv_file_path = os.path.join( csv_file_path = os.path.join(os.getcwd(), "tests/data/account.csv")
os.getcwd(), 'tests/data/account.csv')
csv_content = load.load_file(csv_file_path) csv_content = load.load_file(csv_file_path)
self.assertEqual( self.assertEqual(
csv_content, csv_content,
[ [
{'username': 'test1', 'password': '111111'}, {"username": "test1", "password": "111111"},
{'username': 'test2', 'password': '222222'}, {"username": "test2", "password": "222222"},
{'username': 'test3', 'password': '333333'} {"username": "test3", "password": "333333"},
] ],
) )
def test_load_folder_files(self): def test_load_folder_files(self):
folder = os.path.join(os.getcwd(), 'tests') folder = os.path.join(os.getcwd(), "tests")
file1 = os.path.join(os.getcwd(), 'tests', 'test_utils.py') file1 = os.path.join(os.getcwd(), "tests", "test_utils.py")
file2 = os.path.join(os.getcwd(), 'tests', 'api', 'reset_all.yml') file2 = os.path.join(os.getcwd(), "tests", "api", "reset_all.yml")
files = load.load_folder_files(folder, recursive=False) files = load.load_folder_files(folder, recursive=False)
self.assertEqual(files, []) self.assertEqual(files, [])
@@ -129,25 +128,25 @@ class TestFileLoader(unittest.TestCase):
self.assertEqual([], files) self.assertEqual([], files)
def test_load_dot_env_file(self): def test_load_dot_env_file(self):
dot_env_path = os.path.join( dot_env_path = os.path.join(os.getcwd(), "tests", ".env")
os.getcwd(), "tests", ".env"
)
env_variables_mapping = load.load_dot_env_file(dot_env_path) env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertIn("PROJECT_KEY", env_variables_mapping) self.assertIn("PROJECT_KEY", env_variables_mapping)
self.assertEqual(env_variables_mapping["UserName"], "debugtalk") self.assertEqual(env_variables_mapping["UserName"], "debugtalk")
def test_load_custom_dot_env_file(self): def test_load_custom_dot_env_file(self):
dot_env_path = os.path.join( dot_env_path = os.path.join(os.getcwd(), "tests", "data", "test.env")
os.getcwd(), "tests", "data", "test.env"
)
env_variables_mapping = load.load_dot_env_file(dot_env_path) env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertIn("PROJECT_KEY", env_variables_mapping) self.assertIn("PROJECT_KEY", env_variables_mapping)
self.assertEqual(env_variables_mapping["UserName"], "test") 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): def test_load_env_path_not_exist(self):
dot_env_path = os.path.join( 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) env_variables_mapping = load.load_dot_env_file(dot_env_path)
self.assertEqual(env_variables_mapping, {}) self.assertEqual(env_variables_mapping, {})

View File

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

View File

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

View File

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