change: format code with balck

This commit is contained in:
debugtalk
2020-05-14 14:21:35 +08:00
parent a95d4137c1
commit 1c97e0bbfe
45 changed files with 664 additions and 844 deletions
+20 -16
View File
@@ -36,10 +36,7 @@ class HttpRunner(object):
""" """
self.exception_stage = "initialize HttpRunner()" self.exception_stage = "initialize HttpRunner()"
kwargs = { kwargs = {"failfast": True, "resultclass": report.HtmlTestResult}
"failfast": True,
"resultclass": report.HtmlTestResult
}
logger.remove() logger.remove()
log_level = log_level.upper() log_level = log_level.upper()
@@ -57,6 +54,7 @@ class HttpRunner(object):
def _add_test(test_runner: TestCaseRunner): def _add_test(test_runner: TestCaseRunner):
""" add test to testcase. """ add test to testcase.
""" """
def test(self): def test(self):
try: try:
test_runner.run() test_runner.run()
@@ -79,7 +77,7 @@ class HttpRunner(object):
test_runner = TestCaseRunner().init(testcase) test_runner = TestCaseRunner().init(testcase)
TestSequense = type('TestSequense', (unittest.TestCase,), {}) TestSequense = type("TestSequense", (unittest.TestCase,), {})
test_method = _add_test(test_runner) test_method = _add_test(test_runner)
setattr(TestSequense, "test_method_name", test_method) setattr(TestSequense, "test_method_name", test_method)
@@ -89,7 +87,9 @@ class HttpRunner(object):
return prepared_testcases return prepared_testcases
def _run_suite(self, prepared_testcases: List[unittest.TestSuite]) -> List[TestCaseSummary]: def _run_suite(
self, prepared_testcases: List[unittest.TestSuite]
) -> List[TestCaseSummary]:
""" run prepared testcases """ run prepared testcases
""" """
tests_results: List[TestCaseSummary] = [] tests_results: List[TestCaseSummary] = []
@@ -131,9 +131,7 @@ class HttpRunner(object):
""" """
testsuite_summary = TestSuiteSummary( testsuite_summary = TestSuiteSummary(
success=True, success=True, platform=report.get_platform(), testcases=[]
platform=report.get_platform(),
testcases=[]
) )
testsuite_summary.stat.total = len(tests_results) testsuite_summary.stat.total = len(tests_results)
testsuite_summary.stat.success = 0 testsuite_summary.stat.success = 0
@@ -148,11 +146,16 @@ class HttpRunner(object):
testsuite_summary.success &= testcase_summary.success testsuite_summary.success &= testcase_summary.success
testsuite_summary.testcases.append(testcase_summary) testsuite_summary.testcases.append(testcase_summary)
total_duration = tests_results[-1].time.start_at + tests_results[-1].time.duration \ total_duration = (
tests_results[-1].time.start_at
+ tests_results[-1].time.duration
- tests_results[0].time.start_at - tests_results[0].time.start_at
)
testsuite_summary.time.start_at = tests_results[0].time.start_at testsuite_summary.time.start_at = tests_results[0].time.start_at
testsuite_summary.time.start_at_iso_format = tests_results[0].time.start_at_iso_format testsuite_summary.time.start_at_iso_format = tests_results[
0
].time.start_at_iso_format
testsuite_summary.time.duration = total_duration testsuite_summary.time.duration = total_duration
return testsuite_summary return testsuite_summary
@@ -166,7 +169,7 @@ class HttpRunner(object):
if self.save_tests: if self.save_tests:
utils.dump_json_file( utils.dump_json_file(
tests_mapping, tests_mapping,
utils.prepare_log_file_abs_path(self.test_path, "loaded.json") utils.prepare_log_file_abs_path(self.test_path, "loaded.json"),
) )
# prepare testcases # prepare testcases
@@ -187,13 +190,12 @@ class HttpRunner(object):
if self.save_tests: if self.save_tests:
utils.dump_json_file( utils.dump_json_file(
self._summary.dict(), self._summary.dict(),
utils.prepare_log_file_abs_path(self.test_path, "summary.json") utils.prepare_log_file_abs_path(self.test_path, "summary.json"),
) )
# save variables and export data # save variables and export data
vars_out = self.get_vars_out() vars_out = self.get_vars_out()
utils.dump_json_file( utils.dump_json_file(
vars_out, vars_out, utils.prepare_log_file_abs_path(self.test_path, "io.json")
utils.prepare_log_file_abs_path(self.test_path, "io.json")
) )
return self._summary return self._summary
@@ -266,7 +268,9 @@ 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)
project_working_directory = path_or_tests.get("project_meta", {}).get("PWD", os.getcwd()) project_working_directory = path_or_tests.get("project_meta", {}).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)
+17 -4
View File
@@ -4,12 +4,25 @@ from httprunner.api import HttpRunner
class TestHttpRunner(unittest.TestCase): class TestHttpRunner(unittest.TestCase):
def setUp(self): def setUp(self):
self.runner = HttpRunner() self.runner = HttpRunner()
def test_run_testcase_by_path(self): def test_run_testcase_by_path_request_only(self):
summary = self.runner.run_path("examples/postman_echo/request_methods/") summary = self.runner.run_path(
"examples/postman_echo/request_methods/request_with_variables.yml"
)
self.assertTrue(summary.success) self.assertTrue(summary.success)
self.assertEqual(summary.testcases[0].name, "request methods testcase with variables") self.assertEqual(
summary.testcases[0].name, "request methods testcase with variables"
)
self.assertGreater(summary.stat.total, 1)
def test_run_testcase_by_path_ref_testcase(self):
summary = self.runner.run_path(
"examples/postman_echo/request_methods/request_with_testcase_reference.yml"
)
self.assertTrue(summary.success)
self.assertEqual(
summary.testcases[0].name, "request methods testcase with variables"
)
self.assertGreater(summary.stat.total, 1) self.assertGreater(summary.stat.total, 1)
+1 -7
View File
@@ -8,13 +8,7 @@ app = FastAPI()
@app.get("/hrun/version") @app.get("/hrun/version")
async def get_hrun_version(): async def get_hrun_version():
return { return {"code": 0, "message": "success", "result": {"HttpRunner": __version__}}
"code": 0,
"message": "success",
"result": {
"HttpRunner": __version__
}
}
app.include_router(deps.router) app.include_router(deps.router)
+2 -9
View File
@@ -9,11 +9,7 @@ runner = HttpRunner()
@router.post("/hrun/debug/testcase", tags=["debug"]) @router.post("/hrun/debug/testcase", tags=["debug"])
async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase): async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase):
resp = { resp = {"code": 0, "message": "success", "result": {}}
"code": 0,
"message": "success",
"result": {}
}
project_meta_json = project_meta.dict(by_alias=True) project_meta_json = project_meta.dict(by_alias=True)
if project_meta.debugtalk_py: if project_meta.debugtalk_py:
@@ -27,10 +23,7 @@ async def debug_single_testcase(project_meta: ProjectMeta, testcase: TestCase):
project_meta_json["functions"][func_name] = locals()[func_name] project_meta_json["functions"][func_name] = locals()[func_name]
testcase_json = testcase.dict(by_alias=True) testcase_json = testcase.dict(by_alias=True)
tests_mapping = { tests_mapping = {"project_meta": project_meta_json, "testcases": [testcase_json]}
"project_meta": project_meta_json,
"testcases": [testcase_json]
}
summary = runner.run_tests(tests_mapping) summary = runner.run_tests(tests_mapping)
if not summary["success"]: if not summary["success"]:
+6 -7
View File
@@ -8,13 +8,12 @@ client = TestClient(app)
class TestDebug(unittest.TestCase): class TestDebug(unittest.TestCase):
def test_debug_single_testcase(self): def test_debug_single_testcase(self):
json_data = { json_data = {
"project_meta": { "project_meta": {
"debugtalk_py": "\ndef hello(name):\n print(f'hello, {name}')\n", "debugtalk_py": "\ndef hello(name):\n print(f'hello, {name}')\n",
"variables": {}, "variables": {},
"env": {} "env": {},
}, },
"testcase": { "testcase": {
"config": { "config": {
@@ -24,7 +23,7 @@ class TestDebug(unittest.TestCase):
"variables": {}, "variables": {},
"setup_hooks": [], "setup_hooks": [],
"teardown_hooks": [], "teardown_hooks": [],
"export": [] "export": [],
}, },
"teststeps": [ "teststeps": [
{ {
@@ -38,13 +37,13 @@ class TestDebug(unittest.TestCase):
"cookies": {}, "cookies": {},
"timeout": 30, "timeout": 30,
"allow_redirects": True, "allow_redirects": True,
"verify": False "verify": False,
}, },
"extract": {}, "extract": {},
"validate": [] "validate": [],
}
]
} }
],
},
} }
response = client.post("/hrun/debug/testcase", json=json_data) response = client.post("/hrun/debug/testcase", json=json_data)
assert response.status_code == 200 assert response.status_code == 200
+2 -6
View File
@@ -23,15 +23,11 @@ def stdout_io(stdout=None):
async def debug_python(request: Request): async def debug_python(request: Request):
body = await request.body() body = await request.body()
if request.headers.get('content-transfer-encoding') == "base64": if request.headers.get("content-transfer-encoding") == "base64":
# TODO: decode base64 # TODO: decode base64
pass pass
resp = { resp = {"code": 0, "message": "success", "result": ""}
"code": 0,
"message": "success",
"result": ""
}
try: try:
with stdout_io() as s: with stdout_io() as s:
exec(body, globals()) exec(body, globals())
+2 -10
View File
@@ -10,11 +10,7 @@ router = APIRouter()
@router.get("/hrun/deps", tags=["deps"]) @router.get("/hrun/deps", tags=["deps"])
async def get_installed_dependenies(): async def get_installed_dependenies():
resp = { resp = {"code": 0, "message": "success", "result": {}}
"code": 0,
"message": "success",
"result": {}
}
for p in pkg_resources.working_set: for p in pkg_resources.working_set:
resp["result"][p.project_name] = p.version resp["result"][p.project_name] = p.version
@@ -23,11 +19,7 @@ async def get_installed_dependenies():
@router.post("/hrun/deps", tags=["deps"]) @router.post("/hrun/deps", tags=["deps"])
async def install_dependenies(deps: List[str]): async def install_dependenies(deps: List[str]):
resp = { resp = {"code": 0, "message": "success", "result": {}}
"code": 0,
"message": "success",
"result": {}
}
for dep in deps: for dep in deps:
try: try:
p = subprocess.run(["pip", "install", dep]) p = subprocess.run(["pip", "install", dep])
+3 -3
View File
@@ -13,8 +13,9 @@ 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):
@@ -36,4 +37,3 @@ def sleep(n_secs):
""" sleep n seconds """ sleep n seconds
""" """
time.sleep(n_secs) time.sleep(n_secs)
+33 -30
View File
@@ -6,6 +6,7 @@ if len(sys.argv) >= 2 and sys.argv[1] == "locusts":
# monkey patch ssl at beginning to avoid RecursionError when running locust. # monkey patch ssl at beginning to avoid RecursionError when running locust.
try: try:
from gevent import monkey from gevent import monkey
monkey.patch_ssl() monkey.patch_ssl()
from locust.main import main as _ from locust.main import main as _
except ImportError: except ImportError:
@@ -27,42 +28,42 @@ from httprunner.ext.locusts import init_parser_locusts, main_locusts
def init_parser_run(subparsers): def init_parser_run(subparsers):
sub_parser_run = subparsers.add_parser( sub_parser_run = subparsers.add_parser("run", help="Run HttpRunner testcases.")
"run", help="Run HttpRunner testcases.")
sub_parser_run.add_argument( sub_parser_run.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.",
)
sub_parser_run.add_argument( sub_parser_run.add_argument(
'--log-level', default='INFO', "--log-level", default="INFO", help="Specify logging level, default is INFO."
help="Specify logging level, default is INFO.") )
sub_parser_run.add_argument("--log-file", help="Write logs to specified file path.")
sub_parser_run.add_argument( sub_parser_run.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.",
)
sub_parser_run.add_argument( sub_parser_run.add_argument(
'--dot-env-path', "--report-template", help="Specify report template path."
help="Specify .env file path, which is useful for keeping sensitive data.") )
sub_parser_run.add_argument("--report-dir", help="Specify report save directory.")
sub_parser_run.add_argument( sub_parser_run.add_argument(
'--report-template', "--report-file",
help="Specify report template path.") help="Specify report file path, this has higher priority than specifying report dir.",
)
sub_parser_run.add_argument( sub_parser_run.add_argument(
'--report-dir', "--save-tests",
help="Specify report save directory.") action="store_true",
sub_parser_run.add_argument( default=False,
'--report-file', help="Save loaded/parsed/vars_out/summary json data to JSON files.",
help="Specify report file path, this has higher priority than specifying report dir.") )
sub_parser_run.add_argument(
'--save-tests', action='store_true', default=False,
help="Save loaded/parsed/vars_out/summary json data to JSON files.")
return sub_parser_run return sub_parser_run
def main_run(args): def main_run(args):
runner = HttpRunner( runner = HttpRunner(
save_tests=args.save_tests, save_tests=args.save_tests, log_level=args.log_level, log_file=args.log_file
log_level=args.log_level,
log_file=args.log_file
) )
err_code = 0 err_code = 0
@@ -73,11 +74,13 @@ def main_run(args):
runner.gen_html_report( runner.gen_html_report(
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 testsuite_summary and testsuite_summary.success else 1) err_code |= 0 if testsuite_summary and testsuite_summary.success else 1
except Exception as ex: except Exception as ex:
logger.error(f"!!!!!!!!!! exception stage: {runner.exception_stage} !!!!!!!!!!\n{str(ex)}") logger.error(
f"!!!!!!!!!! exception stage: {runner.exception_stage} !!!!!!!!!!\n{str(ex)}"
)
err_code = 1 err_code = 1
sys.exit(err_code) sys.exit(err_code)
@@ -88,10 +91,10 @@ def main():
""" """
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") )
subparsers = parser.add_subparsers(help='sub-command help') subparsers = parser.add_subparsers(help="sub-command help")
sub_parser_run = init_parser_run(subparsers) sub_parser_run = init_parser_run(subparsers)
sub_parser_scaffold = init_parser_scaffold(subparsers) sub_parser_scaffold = init_parser_scaffold(subparsers)
sub_parser_har2case = init_har2case_parser(subparsers) sub_parser_har2case = init_har2case_parser(subparsers)
@@ -153,5 +156,5 @@ def main_hrun_alias():
main() main()
if __name__ == '__main__': if __name__ == "__main__":
main() main()
+2 -1
View File
@@ -6,7 +6,6 @@ from httprunner.cli import main
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())
+14 -15
View File
@@ -4,8 +4,12 @@ import requests
import urllib3 import urllib3
from loguru import logger from loguru import logger
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.schema import RequestData, ResponseData from httprunner.schema import RequestData, ResponseData
from httprunner.schema import SessionData, ReqRespData from httprunner.schema import SessionData, ReqRespData
@@ -15,9 +19,8 @@ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
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)
@@ -25,6 +28,7 @@ class ApiResponse(Response):
def get_req_resp_record(resp_obj: Response) -> ReqRespData: def get_req_resp_record(resp_obj: Response) -> ReqRespData:
""" get request and response info from Response() object. """ get request and response info from Response() object.
""" """
def log_print(req_or_resp, r_type): def log_print(req_or_resp, r_type):
msg = f"\n================== {r_type} details ==================\n" msg = f"\n================== {r_type} details ==================\n"
for key, value in req_or_resp.dict().items(): for key, value in req_or_resp.dict().items():
@@ -36,14 +40,12 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
method=resp_obj.request.method, method=resp_obj.request.method,
url=resp_obj.request.url, url=resp_obj.request.url,
headers=dict(resp_obj.request.headers), headers=dict(resp_obj.request.headers),
body=resp_obj.request.body body=resp_obj.request.body,
) )
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(request_data.headers).get("content-type")
request_data.headers
).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
request_data.body = "upload file stream (OMITTED)" request_data.body = "upload file stream (OMITTED)"
@@ -76,16 +78,13 @@ def get_req_resp_record(resp_obj: Response) -> ReqRespData:
encoding=resp_obj.encoding, encoding=resp_obj.encoding,
headers=resp_headers, headers=resp_headers,
content_type=content_type, content_type=content_type,
body=response_body body=response_body,
) )
# log response details in debug mode # log response details in debug mode
log_print(response_data, "response") log_print(response_data, "response")
req_resp_data = ReqRespData( req_resp_data = ReqRespData(request=request_data, response=response_data)
request=request_data,
response=response_data
)
return req_resp_data return req_resp_data
@@ -98,6 +97,7 @@ 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.data = SessionData() self.data = SessionData()
@@ -173,8 +173,7 @@ class HttpSession(requests.Session):
# 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.data.req_resps = [ self.data.req_resps = [
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:
+18 -12
View File
@@ -20,19 +20,27 @@ def init_har2case_parser(subparsers):
""" HAR converter: parse command line options and run commands. """ HAR converter: parse command line options and run commands.
""" """
parser = subparsers.add_parser( parser = subparsers.add_parser(
"har2case", help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.") "har2case",
parser.add_argument('har_source_file', nargs='?', help="Convert HAR(HTTP Archive) to YAML/JSON testcases for HttpRunner.",
help="Specify HAR source file") )
parser.add_argument("har_source_file", nargs="?", help="Specify HAR source file")
parser.add_argument( parser.add_argument(
'-2y', '--to-yml', '--to-yaml', "-2y",
dest='to_yaml', action='store_true', "--to-yml",
help="Convert to YAML format, if not specified, convert to JSON format by default.") "--to-yaml",
dest="to_yaml",
action="store_true",
help="Convert to YAML format, if not specified, convert to JSON format by default.",
)
parser.add_argument( parser.add_argument(
'--filter', help="Specify filter keyword, only url include filter string will be converted.") "--filter",
help="Specify filter keyword, only url include filter string will be converted.",
)
parser.add_argument( parser.add_argument(
'--exclude', "--exclude",
help="Specify exclude keyword, url that includes exclude string will be ignored, " help="Specify exclude keyword, url that includes exclude string will be ignored, "
"multiple keywords can be joined with '|'") "multiple keywords can be joined with '|'",
)
return parser return parser
@@ -48,8 +56,6 @@ def main_har2case(args):
sys.exit(1) sys.exit(1)
output_file_type = "YML" if args.to_yaml else "JSON" output_file_type = "YML" if args.to_yaml else "JSON"
HarParser( HarParser(har_source_file, args.filter, args.exclude).gen_testcase(output_file_type)
har_source_file, args.filter, args.exclude
).gen_testcase(output_file_type)
return 0 return 0
+11 -21
View File
@@ -28,12 +28,11 @@ IGNORE_REQUEST_HEADERS = [
":authority", ":authority",
":method", ":method",
":scheme", ":scheme",
":path" ":path",
] ]
class HarParser(object): class HarParser(object):
def __init__(self, har_file_path, filter_str=None, exclude_str=None): def __init__(self, har_file_path, filter_str=None, exclude_str=None):
self.har_file_path = har_file_path self.har_file_path = har_file_path
self.filter_str = filter_str self.filter_str = filter_str
@@ -76,7 +75,7 @@ class HarParser(object):
parsed_object = urlparse.urlparse(url) parsed_object = urlparse.urlparse(url)
if request_params: if request_params:
parsed_object = parsed_object._replace(query='') parsed_object = parsed_object._replace(query="")
teststep_dict["request"]["url"] = parsed_object.geturl() teststep_dict["request"]["url"] = parsed_object.geturl()
teststep_dict["request"]["params"] = request_params teststep_dict["request"]["params"] = request_params
else: else:
@@ -241,7 +240,7 @@ class HarParser(object):
encoding = resp_content_dict.get("encoding") encoding = resp_content_dict.get("encoding")
if encoding and encoding == "base64": if encoding and encoding == "base64":
content = base64.b64decode(text).decode('utf-8') content = base64.b64decode(text).decode("utf-8")
else: else:
content = text content = text
@@ -249,7 +248,9 @@ class HarParser(object):
resp_content_json = json.loads(content) resp_content_json = json.loads(content)
except JSONDecodeError: except JSONDecodeError:
logger.warning( logger.warning(
"response content can not be loaded as json: {}".format(content.encode("utf-8")) "response content can not be loaded as json: {}".format(
content.encode("utf-8")
)
) )
return return
@@ -285,11 +286,7 @@ class HarParser(object):
} }
""" """
teststep_dict = { teststep_dict = {"name": "", "request": {}, "validate": []}
"name": "",
"request": {},
"validate": []
}
self.__make_request_url(teststep_dict, entry_json) self.__make_request_url(teststep_dict, entry_json)
self.__make_request_method(teststep_dict, entry_json) self.__make_request_method(teststep_dict, entry_json)
@@ -302,16 +299,14 @@ class HarParser(object):
def _prepare_config(self): def _prepare_config(self):
""" prepare config block. """ prepare config block.
""" """
return { return {"name": "testcase description", "variables": {}}
"name": "testcase description",
"variables": {}
}
def _prepare_teststeps(self): def _prepare_teststeps(self):
""" make teststep list. """ make teststep list.
teststeps list are parsed from HAR log entries list. teststeps list are parsed from HAR log entries list.
""" """
def is_exclude(url, exclude_str): def is_exclude(url, exclude_str):
exclude_str_list = exclude_str.split("|") exclude_str_list = exclude_str.split("|")
for exclude_str in exclude_str_list: for exclude_str in exclude_str_list:
@@ -330,9 +325,7 @@ class HarParser(object):
if is_exclude(url, self.exclude_str): if is_exclude(url, self.exclude_str):
continue continue
teststeps.append( teststeps.append(self._prepare_teststep(entry_json))
self._prepare_teststep(entry_json)
)
return teststeps return teststeps
@@ -344,10 +337,7 @@ class HarParser(object):
config = self._prepare_config() config = self._prepare_config()
teststeps = self._prepare_teststeps() teststeps = self._prepare_teststeps()
testcase = { testcase = {"config": config, "teststeps": teststeps}
"config": config,
"teststeps": teststeps
}
return testcase return testcase
def gen_testcase(self, file_type="JSON"): def gen_testcase(self, file_type="JSON"):
+25 -66
View File
@@ -6,7 +6,6 @@ from httprunner.ext.har2case.utils_test import TestUtils
class TestHar(TestUtils): class TestHar(TestUtils):
def setUp(self): def setUp(self):
self.har_path = os.path.join(os.path.dirname(__file__), "data", "demo.har") self.har_path = os.path.join(os.path.dirname(__file__), "data", "demo.har")
self.har_parser = HarParser(self.har_path) self.har_parser = HarParser(self.har_path)
@@ -22,18 +21,10 @@ class TestHar(TestUtils):
validator["eq"][0]: validator["eq"][1] validator["eq"][0]: validator["eq"][1]
for validator in teststep_dict["validate"] for validator in teststep_dict["validate"]
} }
self.assertEqual( self.assertEqual(validators_mapping["status_code"], 200)
validators_mapping["status_code"], 200 self.assertEqual(validators_mapping["content.IsSuccess"], True)
) self.assertEqual(validators_mapping["content.Code"], 200)
self.assertEqual( self.assertEqual(validators_mapping["content.Message"], None)
validators_mapping["content.IsSuccess"], True
)
self.assertEqual(
validators_mapping["content.Code"], 200
)
self.assertEqual(
validators_mapping["content.Message"], None
)
def test_prepare_teststeps(self): def test_prepare_teststeps(self):
teststeps = self.har_parser._prepare_teststeps() teststeps = self.har_parser._prepare_teststeps()
@@ -43,16 +34,14 @@ class TestHar(TestUtils):
self.assertIn("validate", teststeps[0]) self.assertIn("validate", teststeps[0])
def test_gen_testcase_yaml(self): def test_gen_testcase_yaml(self):
yaml_file = os.path.join( yaml_file = os.path.join(os.path.dirname(__file__), "data", "demo.yaml")
os.path.dirname(__file__), "data", "demo.yaml")
self.har_parser.gen_testcase(file_type="YAML") self.har_parser.gen_testcase(file_type="YAML")
self.assertTrue(os.path.isfile(yaml_file)) self.assertTrue(os.path.isfile(yaml_file))
os.remove(yaml_file) os.remove(yaml_file)
def test_gen_testcase_json(self): def test_gen_testcase_json(self):
json_file = os.path.join( json_file = os.path.join(os.path.dirname(__file__), "data", "demo.json")
os.path.dirname(__file__), "data", "demo.json")
self.har_parser.gen_testcase(file_type="JSON") self.har_parser.gen_testcase(file_type="JSON")
self.assertTrue(os.path.isfile(json_file)) self.assertTrue(os.path.isfile(json_file))
@@ -64,7 +53,7 @@ class TestHar(TestUtils):
teststeps = har_parser._prepare_teststeps() teststeps = har_parser._prepare_teststeps()
self.assertEqual( self.assertEqual(
teststeps[0]["request"]["url"], teststeps[0]["request"]["url"],
"https://httprunner.top/api/v1/Account/Login" "https://httprunner.top/api/v1/Account/Login",
) )
filter_str = "debugtalk" filter_str = "debugtalk"
@@ -78,7 +67,7 @@ class TestHar(TestUtils):
teststeps = har_parser._prepare_teststeps() teststeps = har_parser._prepare_teststeps()
self.assertEqual( self.assertEqual(
teststeps[0]["request"]["url"], teststeps[0]["request"]["url"],
"https://httprunner.top/api/v1/Account/Login" "https://httprunner.top/api/v1/Account/Login",
) )
exclude_str = "httprunner" exclude_str = "httprunner"
@@ -98,20 +87,13 @@ class TestHar(TestUtils):
self.assertEqual(teststeps, []) self.assertEqual(teststeps, [])
def test_make_request_data_params(self): def test_make_request_data_params(self):
testcase_dict = { testcase_dict = {"name": "", "request": {}, "validate": []}
"name": "",
"request": {},
"validate": []
}
entry_json = { entry_json = {
"request": { "request": {
"method": "POST", "method": "POST",
"postData": { "postData": {
"mimeType": "application/x-www-form-urlencoded; charset=utf-8", "mimeType": "application/x-www-form-urlencoded; charset=utf-8",
"params": [ "params": [{"name": "a", "value": 1}, {"name": "b", "value": "2"}],
{"name": "a", "value": 1},
{"name": "b", "value": "2"}
]
}, },
} }
} }
@@ -120,53 +102,32 @@ class TestHar(TestUtils):
self.assertEqual(testcase_dict["request"]["data"]["b"], "2") self.assertEqual(testcase_dict["request"]["data"]["b"], "2")
def test_make_request_data_json(self): def test_make_request_data_json(self):
testcase_dict = { testcase_dict = {"name": "", "request": {}, "validate": []}
"name": "",
"request": {},
"validate": []
}
entry_json = { entry_json = {
"request": { "request": {
"method": "POST", "method": "POST",
"postData": { "postData": {
"mimeType": "application/json; charset=utf-8", "mimeType": "application/json; charset=utf-8",
"text": "{\"a\":\"1\",\"b\":\"2\"}" "text": '{"a":"1","b":"2"}',
}, },
} }
} }
self.har_parser._make_request_data(testcase_dict, entry_json) self.har_parser._make_request_data(testcase_dict, entry_json)
self.assertEqual( self.assertEqual(testcase_dict["request"]["json"], {"a": "1", "b": "2"})
testcase_dict["request"]["json"],
{'a': '1', 'b': '2'}
)
def test_make_request_data_text_empty(self): def test_make_request_data_text_empty(self):
testcase_dict = { testcase_dict = {"name": "", "request": {}, "validate": []}
"name": "",
"request": {},
"validate": []
}
entry_json = { entry_json = {
"request": { "request": {
"method": "POST", "method": "POST",
"postData": { "postData": {"mimeType": "application/json; charset=utf-8", "text": ""},
"mimeType": "application/json; charset=utf-8",
"text": ""
},
} }
} }
self.har_parser._make_request_data(testcase_dict, entry_json) self.har_parser._make_request_data(testcase_dict, entry_json)
self.assertEqual( self.assertEqual(testcase_dict["request"]["data"], "")
testcase_dict["request"]["data"],
""
)
def test_make_validate(self): def test_make_validate(self):
testcase_dict = { testcase_dict = {"name": "", "request": {}, "validate": []}
"name": "",
"request": {},
"validate": []
}
entry_json = { entry_json = {
"request": {}, "request": {},
"response": { "response": {
@@ -174,7 +135,7 @@ class TestHar(TestUtils):
"headers": [ "headers": [
{ {
"name": "Content-Type", "name": "Content-Type",
"value": "application/json; charset=utf-8" "value": "application/json; charset=utf-8",
}, },
], ],
"content": { "content": {
@@ -182,23 +143,21 @@ class TestHar(TestUtils):
"mimeType": "application/json; charset=utf-8", "mimeType": "application/json; charset=utf-8",
# raw response content text is application/jose type # raw response content text is application/jose type
"text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV", "text": "ZXlKaGJHY2lPaUpTVTBFeFh6VWlMQ0psYm1NaU9pSkJNVEk0UTBKRExV",
"encoding": "base64" "encoding": "base64",
} },
} },
} }
self.har_parser._make_validate(testcase_dict, entry_json) self.har_parser._make_validate(testcase_dict, entry_json)
self.assertEqual( self.assertEqual(testcase_dict["validate"][0], {"eq": ["status_code", 200]})
testcase_dict["validate"][0],
{"eq": ["status_code", 200]}
)
self.assertEqual( self.assertEqual(
testcase_dict["validate"][1], testcase_dict["validate"][1],
{"eq": ["headers.Content-Type", "application/json; charset=utf-8"]} {"eq": ["headers.Content-Type", "application/json; charset=utf-8"]},
) )
def test_make_testcase(self): def test_make_testcase(self):
har_path = os.path.join( har_path = os.path.join(
os.path.dirname(__file__), "data", "demo-quickstart.har") os.path.dirname(__file__), "data", "demo-quickstart.har"
)
har_parser = HarParser(har_path) har_parser = HarParser(har_path)
testcase = har_parser._make_testcase() testcase = har_parser._make_testcase()
self.assertIsInstance(testcase, dict) self.assertIsInstance(testcase, dict)
+9 -11
View File
@@ -50,10 +50,9 @@ def x_www_form_urlencoded(post_data):
""" """
if isinstance(post_data, dict): if isinstance(post_data, dict):
return "&".join([ return "&".join(
u"{}={}".format(key, value) [u"{}={}".format(key, value) for key, value in post_data.items()]
for key, value in post_data.items() )
])
else: else:
return post_data return post_data
@@ -98,10 +97,7 @@ def convert_list_to_dict(origin_list):
{"v": "1", "w": "2"} {"v": "1", "w": "2"}
""" """
return { return {item["name"]: item.get("value") for item in origin_list}
item["name"]: item.get("value")
for item in origin_list
}
def dump_yaml(testcase, yaml_file): def dump_yaml(testcase, yaml_file):
@@ -109,8 +105,10 @@ def dump_yaml(testcase, yaml_file):
""" """
logging.info("dump testcase to YAML format.") logging.info("dump testcase to YAML format.")
with io.open(yaml_file, 'w', encoding="utf-8") as outfile: with io.open(yaml_file, "w", encoding="utf-8") as outfile:
yaml.dump(testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4) yaml.dump(
testcase, outfile, allow_unicode=True, default_flow_style=False, indent=4
)
logging.info("Generate YAML testcase successfully: {}".format(yaml_file)) logging.info("Generate YAML testcase successfully: {}".format(yaml_file))
@@ -120,7 +118,7 @@ def dump_json(testcase, json_file):
""" """
logging.info("dump testcase to JSON format.") logging.info("dump testcase to JSON format.")
with io.open(json_file, 'w', encoding="utf-8") as outfile: with io.open(json_file, "w", encoding="utf-8") as outfile:
my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4) my_json_str = json.dumps(testcase, ensure_ascii=False, indent=4)
if isinstance(my_json_str, bytes): if isinstance(my_json_str, bytes):
my_json_str = my_json_str.decode("utf-8") my_json_str = my_json_str.decode("utf-8")
+7 -12
View File
@@ -6,11 +6,11 @@ from httprunner.ext.har2case import utils
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
@staticmethod @staticmethod
def create_har_file(file_name, content): def create_har_file(file_name, content):
file_path = os.path.join( file_path = os.path.join(
os.path.dirname(__file__), "data", "{}.har".format(file_name)) os.path.dirname(__file__), "data", "{}.har".format(file_name)
)
with open(file_path, "w") as f: with open(file_path, "w") as f:
f.write(json.dumps(content)) f.write(json.dumps(content))
@@ -24,7 +24,9 @@ class TestUtils(unittest.TestCase):
self.assertIn("response", log_entries[0]) self.assertIn("response", log_entries[0])
def test_load_har_log_key_error(self): def test_load_har_log_key_error(self):
empty_json_file_path = TestUtils.create_har_file(file_name="empty_json", content={}) empty_json_file_path = TestUtils.create_har_file(
file_name="empty_json", content={}
)
with self.assertRaises(SystemExit): with self.assertRaises(SystemExit):
utils.load_har_log_entries(empty_json_file_path) utils.load_har_log_entries(empty_json_file_path)
os.remove(empty_json_file_path) os.remove(empty_json_file_path)
@@ -35,21 +37,14 @@ class TestUtils(unittest.TestCase):
utils.load_har_log_entries(empty_file_path) utils.load_har_log_entries(empty_file_path)
os.remove(empty_file_path) os.remove(empty_file_path)
# def test_x_www_form_urlencoded(self): # def test_x_www_form_urlencoded(self):
# origin_dict = {"a":1, "b": "2"} # origin_dict = {"a":1, "b": "2"}
# self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict)) # self.assertIn("a=1", utils.x_www_form_urlencoded(origin_dict))
# self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict)) # self.assertIn("b=2", utils.x_www_form_urlencoded(origin_dict))
def test_convert_list_to_dict(self): def test_convert_list_to_dict(self):
origin_list = [ origin_list = [{"name": "v", "value": "1"}, {"name": "w", "value": "2"}]
{"name": "v", "value": "1"}, self.assertEqual(utils.convert_list_to_dict(origin_list), {"v": "1", "w": "2"})
{"name": "w", "value": "2"}
]
self.assertEqual(
utils.convert_list_to_dict(origin_list),
{"v": "1", "w": "2"}
)
def test_convert_x_www_form_urlencoded_to_dict(self): def test_convert_x_www_form_urlencoded_to_dict(self):
origin_str = "a=1&b=2" origin_str = "a=1&b=2"
+23 -11
View File
@@ -4,27 +4,39 @@ import sys
from loguru import logger from loguru import logger
from httprunner import __version__ from httprunner import __version__
from httprunner.ext.locusts.core import start_locust_main, parse_locustfile, quick_run_locusts, start_master, \ from httprunner.ext.locusts.core import (
start_slaves start_locust_main,
parse_locustfile,
quick_run_locusts,
start_master,
start_slaves,
)
CPU_COUNT = multiprocessing.cpu_count() CPU_COUNT = multiprocessing.cpu_count()
def init_parser_locusts(subparsers): def init_parser_locusts(subparsers):
sub_parser_locusts = subparsers.add_parser( sub_parser_locusts = subparsers.add_parser(
"locusts", help="Run load test with locust.") "locusts", help="Run load test with locust."
)
sub_parser_locusts.add_argument( sub_parser_locusts.add_argument(
'--locust-help', action='store_true', default=False, "--locust-help", action="store_true", default=False, help="Show locust help."
help="Show locust help.") )
sub_parser_locusts.add_argument('test_file', nargs='?',
help="Specify YAML/JSON testcase file.")
sub_parser_locusts.add_argument( sub_parser_locusts.add_argument(
"--master", action='store_true', default=False, help="Start locust master.") "test_file", nargs="?", help="Specify YAML/JSON testcase file."
)
sub_parser_locusts.add_argument( sub_parser_locusts.add_argument(
"--slaves", type=int, help="Specify locust slave number.") "--master", action="store_true", default=False, help="Start locust master."
)
sub_parser_locusts.add_argument( sub_parser_locusts.add_argument(
"--quickstart", action='store_true', default=False, "--slaves", type=int, help="Specify locust slave number."
help=f"Start locust master with {CPU_COUNT} slaves.") )
sub_parser_locusts.add_argument(
"--quickstart",
action="store_true",
default=False,
help=f"Start locust master with {CPU_COUNT} slaves.",
)
return sub_parser_locusts return sub_parser_locusts
+10 -10
View File
@@ -18,7 +18,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
@@ -31,16 +31,17 @@ 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
@@ -49,6 +50,7 @@ def gen_locustfile(testcase_file_path):
def start_locust_main(): def start_locust_main():
logger.info(f"run command: {sys.argv}") logger.info(f"run command: {sys.argv}")
from locust.main import main from locust.main import main
main() main()
@@ -94,7 +96,5 @@ def quick_run_locusts(slave_num):
logger.info(f"Start locust master with {slave_num} slaves ...") logger.info(f"Start locust master with {slave_num} slaves ...")
processes = init_slave_processes(slave_num) processes = init_slave_processes(slave_num)
processes.append( processes.append(multiprocessing.Process(target=start_master, args=(sys.argv,)))
multiprocessing.Process(target=start_master, args=(sys.argv,))
)
[process.join() for process in processes] [process.join() for process in processes]
@@ -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,
) )
+2 -4
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.path.dirname(__file__), "data", "demo_locusts.yml")
os.path.dirname(__file__), "data", "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)
+21 -18
View File
@@ -6,8 +6,11 @@ from loguru import logger
def init_parser_scaffold(subparsers): def init_parser_scaffold(subparsers):
sub_parser_scaffold = subparsers.add_parser( sub_parser_scaffold = subparsers.add_parser(
"startproject", help="Create a new project with template structure.") "startproject", help="Create a new project with template structure."
sub_parser_scaffold.add_argument("project_name", type=str, nargs="?", help="Specify new project name.") )
sub_parser_scaffold.add_argument(
"project_name", type=str, nargs="?", help="Specify new project name."
)
return sub_parser_scaffold return sub_parser_scaffold
@@ -15,7 +18,9 @@ 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.warning(f"Folder {project_name} exists, please specify a new folder name.") logger.warning(
f"Folder {project_name} exists, please specify a new folder name."
)
return return
logger.info(f"Start to create new project: {project_name}") logger.info(f"Start to create new project: {project_name}")
@@ -27,7 +32,7 @@ def create_scaffold(project_name):
logger.info(msg) logger.info(msg)
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 = f"created file: {path}" msg = f"created file: {path}"
logger.info(msg) logger.info(msg)
@@ -92,24 +97,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"))
@@ -117,8 +114,14 @@ 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)
-1
View File
@@ -6,7 +6,6 @@ from httprunner.ext.scaffold import create_scaffold
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
def test_create_scaffold(self): def test_create_scaffold(self):
project_name = "projectABC" project_name = "projectABC"
create_scaffold(project_name) create_scaffold(project_name)
+5 -2
View File
@@ -96,7 +96,9 @@ 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"
@@ -122,6 +124,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 +133,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
+5 -3
View File
@@ -11,8 +11,10 @@ HttpRunner loader
from httprunner.loader.buildup import load_cases, load_project_data from httprunner.loader.buildup import load_cases, load_project_data
from httprunner.loader.check import is_test_path from httprunner.loader.check import is_test_path
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.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,
)
__all__ = [ __all__ = [
"is_test_path", "is_test_path",
@@ -21,5 +23,5 @@ __all__ = [
"load_csv_file", "load_csv_file",
"load_builtin_functions", "load_builtin_functions",
"load_project_data", "load_project_data",
"load_cases" "load_cases",
] ]
+22 -21
View File
@@ -4,14 +4,18 @@ import os
from loguru import logger from loguru import logger
from httprunner import exceptions, utils from httprunner import exceptions, utils
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():
@@ -71,10 +75,7 @@ def __extend_with_testcase_ref(raw_testinfo):
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)
# TODO: validate with pydantic # TODO: validate with pydantic
@@ -82,7 +83,8 @@ def __extend_with_testcase_ref(raw_testinfo):
testcase_dict = load_testcase(loaded_testcase) testcase_dict = load_testcase(loaded_testcase)
else: else:
raise exceptions.FileFormatError( raise exceptions.FileFormatError(
f"Invalid format testcase: {testcase_path}") f"Invalid format testcase: {testcase_path}"
)
tests_def_mapping["testcases"][testcase_path] = testcase_dict tests_def_mapping["testcases"][testcase_path] = testcase_dict
else: else:
@@ -174,10 +176,7 @@ def load_testcase(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
@@ -305,7 +304,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_meta = {} project_meta = {}
@@ -325,7 +326,9 @@ def load_project_data(test_path, dot_env_path=None):
# locate PWD and load debugtalk.py functions # locate PWD and load debugtalk.py functions
project_meta["PWD"] = project_working_directory project_meta["PWD"] = project_working_directory
project_meta["functions"] = debugtalk_functions project_meta["functions"] = debugtalk_functions
project_meta["test_path"] = os.path.abspath(test_path)[len(project_working_directory) + 1:] project_meta["test_path"] = os.path.abspath(test_path)[
len(project_working_directory) + 1 :
]
return project_meta return project_meta
@@ -384,9 +387,7 @@ def load_cases(path: str, dot_env_path: str = None):
""" """
tests_mapping = { tests_mapping = {"project_meta": load_project_data(path, dot_env_path)}
"project_meta": load_project_data(path, dot_env_path)
}
def __load_file_content(path): def __load_file_content(path):
loaded_content = None loaded_content = None
+46 -85
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_meta = buildup.load_project_data(os.path.join(os.getcwd(), "httprunner")) project_meta = buildup.load_project_data(
os.path.join(os.getcwd(), "httprunner")
)
self.assertNotIn("alter_response", project_meta["functions"]) self.assertNotIn("alter_response", project_meta["functions"])
project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests")) project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
@@ -28,33 +28,23 @@ class TestModuleLoader(unittest.TestCase):
project_meta = buildup.load_project_data("tests/data/demo_testcase.yml") project_meta = buildup.load_project_data("tests/data/demo_testcase.yml")
project_working_directory = project_meta["PWD"] project_working_directory = project_meta["PWD"]
debugtalk_functions = project_meta["functions"] debugtalk_functions = project_meta["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_meta = buildup.load_project_data("tests/base.py") project_meta = buildup.load_project_data("tests/base.py")
project_working_directory = project_meta["PWD"] project_working_directory = project_meta["PWD"]
debugtalk_functions = project_meta["functions"] debugtalk_functions = project_meta["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_meta = buildup.load_project_data("httprunner/__init__.py") project_meta = buildup.load_project_data("httprunner/__init__.py")
project_working_directory = project_meta["PWD"] project_working_directory = project_meta["PWD"]
debugtalk_functions = project_meta["functions"] debugtalk_functions = project_meta["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_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests")) cls.project_meta = buildup.load_project_data(os.path.join(os.getcwd(), "tests"))
@@ -64,15 +54,10 @@ class TestSuiteLoader(unittest.TestCase):
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 +67,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 +86,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,113 +98,100 @@ 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_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)
api_list = tests_mapping["apis"] api_list = tests_mapping["apis"]
self.assertEqual(len(api_list), 1) self.assertEqual(len(api_list), 1)
self.assertEqual(api_list[0]["request"]["url"], "/api/users/$uid") self.assertEqual(api_list[0]["request"]["url"], "/api/users/$uid")
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_meta"]["functions"])
"sum_two", self.assertEqual(
tests_mapping["project_meta"]["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_meta = tests_mapping["project_meta"] project_meta = tests_mapping["project_meta"]
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_meta["functions"]) self.assertIn("gen_md5", project_meta["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_meta = tests_mapping["project_meta"] project_meta = tests_mapping["project_meta"]
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)
@@ -232,11 +199,5 @@ 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_meta["functions"]) self.assertIn("gen_md5", self.project_meta["functions"])
self.assertEqual(self.project_meta["env"]["PROJECT_KEY"], "ABCDEFGH") self.assertEqual(self.project_meta["env"]["PROJECT_KEY"], "ABCDEFGH")
self.assertEqual( self.assertEqual(os.path.basename(self.project_meta["PWD"]), "tests")
os.path.basename(self.project_meta["PWD"]), self.assertEqual(os.path.basename(self.project_meta["test_path"]), "") # FIXME
"tests"
)
self.assertEqual(
os.path.basename(self.project_meta["test_path"]),
"" # FIXME
)
+1 -1
View File
@@ -30,7 +30,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:
+8 -9
View File
@@ -14,7 +14,7 @@ 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
@@ -22,7 +22,7 @@ except AttributeError:
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:
@@ -35,7 +35,7 @@ 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 json.JSONDecodeError: except json.JSONDecodeError:
@@ -81,7 +81,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)
@@ -94,9 +94,9 @@ def load_file(file_path):
raise exceptions.FileNotFound(f"{file_path} does not exist.") raise exceptions.FileNotFound(f"{file_path} does not exist.")
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)
@@ -132,7 +132,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)
@@ -172,7 +172,7 @@ def load_dot_env_file(dot_env_path):
logger.info(f"Loading environment variables from {dot_env_path}") logger.info(f"Loading environment variables from {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:
@@ -216,4 +216,3 @@ def load_builtin_functions():
""" load builtin module functions """ load builtin module functions
""" """
return load_module_functions(builtin) return load_module_functions(builtin)
+25 -32
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,40 +57,38 @@ 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_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, [])
@@ -107,25 +104,21 @@ 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, {})
+5 -9
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"),
) )
+25 -23
View File
@@ -68,9 +68,7 @@ def regex_findall_variables(content: Text) -> List[Text]:
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 []
@@ -160,20 +158,17 @@ def parse_function_params(params: Text) -> Dict:
{'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))
@@ -181,7 +176,9 @@ def parse_function_params(params: Text) -> Dict:
return function_meta return function_meta
def get_mapping_variable(variable_name: Text, variables_mapping: VariablesMapping) -> Any: def get_mapping_variable(
variable_name: Text, variables_mapping: VariablesMapping
) -> Any:
""" get variable from variables_mapping. """ get variable from variables_mapping.
Args: Args:
@@ -199,10 +196,14 @@ def get_mapping_variable(variable_name: Text, variables_mapping: VariablesMappin
try: try:
return variables_mapping[variable_name] return variables_mapping[variable_name]
except KeyError: except KeyError:
raise exceptions.VariableNotFound(f"{variable_name} not found in {variables_mapping}") raise exceptions.VariableNotFound(
f"{variable_name} not found in {variables_mapping}"
)
def get_mapping_function(function_name: Text, functions_mapping: FunctionsMapping) -> Callable: def get_mapping_function(
function_name: Text, functions_mapping: FunctionsMapping
) -> Callable:
""" 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.
@@ -229,6 +230,7 @@ def get_mapping_function(function_name: Text, functions_mapping: FunctionsMappin
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:
@@ -250,7 +252,8 @@ def get_mapping_function(function_name: Text, functions_mapping: FunctionsMappin
def parse_string( def parse_string(
raw_string: Text, raw_string: Text,
variables_mapping: VariablesMapping, variables_mapping: VariablesMapping,
functions_mapping: FunctionsMapping) -> Any: functions_mapping: FunctionsMapping,
) -> Any:
""" parse string content with variables and functions mapping. """ parse string content with variables and functions mapping.
Args: Args:
@@ -346,7 +349,8 @@ def parse_string(
def parse_data( def parse_data(
raw_data: Any, raw_data: Any,
variables_mapping: VariablesMapping = None, variables_mapping: VariablesMapping = None,
functions_mapping: FunctionsMapping = None) -> Any: functions_mapping: FunctionsMapping = None,
) -> Any:
""" parse raw data with evaluated variables mapping. """ parse raw 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.
""" """
@@ -359,8 +363,7 @@ def parse_data(
elif isinstance(raw_data, (list, set, tuple)): elif isinstance(raw_data, (list, set, tuple)):
return [ return [
parse_data(item, variables_mapping, functions_mapping) parse_data(item, variables_mapping, functions_mapping) for item in raw_data
for item in raw_data
] ]
elif isinstance(raw_data, dict): elif isinstance(raw_data, dict):
@@ -378,8 +381,8 @@ def parse_data(
def parse_variables_mapping( def parse_variables_mapping(
variables_mapping: VariablesMapping, variables_mapping: VariablesMapping, functions_mapping: FunctionsMapping = None
functions_mapping: FunctionsMapping = None) -> VariablesMapping: ) -> VariablesMapping:
parsed_variables: VariablesMapping = {} parsed_variables: VariablesMapping = {}
@@ -401,9 +404,7 @@ def parse_variables_mapping(
# check if reference variable not in variables_mapping # check if reference variable not in variables_mapping
not_defined_variables = [ not_defined_variables = [
v_name v_name for v_name in variables if v_name not in variables_mapping
for v_name in variables
if v_name not in variables_mapping
] ]
if not_defined_variables: if not_defined_variables:
# e.g. {"varA": "123$varB", "varB": "456$varC"} # e.g. {"varA": "123$varB", "varB": "456$varC"}
@@ -412,7 +413,8 @@ def parse_variables_mapping(
try: try:
parsed_value = parse_data( parsed_value = parse_data(
var_value, parsed_variables, functions_mapping) var_value, parsed_variables, functions_mapping
)
except exceptions.VariableNotFound: except exceptions.VariableNotFound:
continue continue
+128 -217
View File
@@ -6,27 +6,15 @@ from httprunner.exceptions import VariableNotFound, FunctionNotFound
class TestParserBasic(unittest.TestCase): class TestParserBasic(unittest.TestCase):
def test_parse_variables_mapping(self): def test_parse_variables_mapping(self):
variables = { variables = {"varA": "$varB", "varB": "$varC", "varC": "123", "a": 1, "b": 2}
"varA": "$varB",
"varB": "$varC",
"varC": "123",
"a": 1,
"b": 2
}
parsed_variables = parser.parse_variables_mapping(variables) parsed_variables = parser.parse_variables_mapping(variables)
print(parsed_variables) print(parsed_variables)
self.assertEqual(parsed_variables["varA"], "123") self.assertEqual(parsed_variables["varA"], "123")
self.assertEqual(parsed_variables["varB"], "123") self.assertEqual(parsed_variables["varB"], "123")
def test_parse_variables_mapping_exception(self): def test_parse_variables_mapping_exception(self):
variables = { variables = {"varA": "$varB", "varB": "$varC", "a": 1, "b": 2}
"varA": "$varB",
"varB": "$varC",
"a": 1,
"b": 2
}
with self.assertRaises(VariableNotFound): with self.assertRaises(VariableNotFound):
parser.parse_variables_mapping(variables) parser.parse_variables_mapping(variables)
@@ -38,125 +26,77 @@ class TestParserBasic(unittest.TestCase):
self.assertEqual(parser.parse_string_value("${func}"), "${func}") self.assertEqual(parser.parse_string_value("${func}"), "${func}")
def test_extract_variables(self): def test_extract_variables(self):
self.assertEqual( self.assertEqual(parser.extract_variables("$var"), {"var"})
parser.extract_variables("$var"), self.assertEqual(parser.extract_variables("$var123"), {"var123"})
{"var"} self.assertEqual(parser.extract_variables("$var_name"), {"var_name"})
) self.assertEqual(parser.extract_variables("var"), set())
self.assertEqual( self.assertEqual(parser.extract_variables("a$var"), {"var"})
parser.extract_variables("$var123"), self.assertEqual(parser.extract_variables("$v ar"), {"v"})
{"var123"} self.assertEqual(parser.extract_variables(" "), set())
) self.assertEqual(parser.extract_variables("$abc*"), {"abc"})
self.assertEqual( self.assertEqual(parser.extract_variables("${func()}"), set())
parser.extract_variables("$var_name"), self.assertEqual(parser.extract_variables("${func(1,2)}"), set())
{"var_name"}
)
self.assertEqual(
parser.extract_variables("var"),
set()
)
self.assertEqual(
parser.extract_variables("a$var"),
{"var"}
)
self.assertEqual(
parser.extract_variables("$v ar"),
{"v"}
)
self.assertEqual(
parser.extract_variables(" "),
set()
)
self.assertEqual(
parser.extract_variables("$abc*"),
{"abc"}
)
self.assertEqual(
parser.extract_variables("${func()}"),
set()
)
self.assertEqual(
parser.extract_variables("${func(1,2)}"),
set()
)
self.assertEqual( self.assertEqual(
parser.extract_variables("${gen_md5($TOKEN, $data, $random)}"), parser.extract_variables("${gen_md5($TOKEN, $data, $random)}"),
{"TOKEN", "data", "random"} {"TOKEN", "data", "random"},
) )
def test_parse_function_params(self): def test_parse_function_params(self):
self.assertEqual(parser.parse_function_params(""), {"args": [], "kwargs": {}})
self.assertEqual(parser.parse_function_params("5"), {"args": [5], "kwargs": {}})
self.assertEqual( self.assertEqual(
parser.parse_function_params(""), parser.parse_function_params("1, 2"), {"args": [1, 2], "kwargs": {}}
{'args': [], 'kwargs': {}}
)
self.assertEqual(
parser.parse_function_params("5"),
{'args': [5], 'kwargs': {}}
)
self.assertEqual(
parser.parse_function_params("1, 2"),
{'args': [1, 2], 'kwargs': {}}
) )
self.assertEqual( self.assertEqual(
parser.parse_function_params("a=1, b=2"), parser.parse_function_params("a=1, b=2"),
{'args': [], 'kwargs': {'a': 1, 'b': 2}} {"args": [], "kwargs": {"a": 1, "b": 2}},
) )
self.assertEqual( self.assertEqual(
parser.parse_function_params("a= 1, b =2"), parser.parse_function_params("a= 1, b =2"),
{'args': [], 'kwargs': {'a': 1, 'b': 2}} {"args": [], "kwargs": {"a": 1, "b": 2}},
) )
self.assertEqual( self.assertEqual(
parser.parse_function_params("1, 2, a=3, b=4"), parser.parse_function_params("1, 2, a=3, b=4"),
{'args': [1, 2], 'kwargs': {'a': 3, 'b': 4}} {"args": [1, 2], "kwargs": {"a": 3, "b": 4}},
) )
self.assertEqual( self.assertEqual(
parser.parse_function_params("$request, 123"), parser.parse_function_params("$request, 123"),
{'args': ["$request", 123], 'kwargs': {}} {"args": ["$request", 123], "kwargs": {}},
)
self.assertEqual(
parser.parse_function_params(" "),
{'args': [], 'kwargs': {}}
) )
self.assertEqual(parser.parse_function_params(" "), {"args": [], "kwargs": {}})
self.assertEqual( self.assertEqual(
parser.parse_function_params("hello world, a=3, b=4"), parser.parse_function_params("hello world, a=3, b=4"),
{'args': ["hello world"], 'kwargs': {'a': 3, 'b': 4}} {"args": ["hello world"], "kwargs": {"a": 3, "b": 4}},
) )
self.assertEqual( self.assertEqual(
parser.parse_function_params("$request, 12 3"), parser.parse_function_params("$request, 12 3"),
{'args': ["$request", '12 3'], 'kwargs': {}} {"args": ["$request", "12 3"], "kwargs": {}},
) )
def test_extract_functions(self): def test_extract_functions(self):
self.assertEqual(parser.regex_findall_functions("${func()}"), [("func", "")])
self.assertEqual(parser.regex_findall_functions("${func(5)}"), [("func", "5")])
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("${func()}"), parser.regex_findall_functions("${func(a=1, b=2)}"), [("func", "a=1, b=2")]
[("func", "")]
)
self.assertEqual(
parser.regex_findall_functions("${func(5)}"),
[("func", "5")]
)
self.assertEqual(
parser.regex_findall_functions("${func(a=1, b=2)}"),
[("func", "a=1, b=2")]
) )
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("${func(1, $b, c=$x, d=4)}"), parser.regex_findall_functions("${func(1, $b, c=$x, d=4)}"),
[("func", "1, $b, c=$x, d=4")] [("func", "1, $b, c=$x, d=4")],
) )
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("/api/1000?_t=${get_timestamp()}"), parser.regex_findall_functions("/api/1000?_t=${get_timestamp()}"),
[("get_timestamp", "")] [("get_timestamp", "")],
) )
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("/api/${add(1, 2)}"), parser.regex_findall_functions("/api/${add(1, 2)}"), [("add", "1, 2")]
[("add", "1, 2")]
) )
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), parser.regex_findall_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"),
[('add', '1, 2'), ('get_timestamp', '')] [("add", "1, 2"), ("get_timestamp", "")],
) )
self.assertEqual( self.assertEqual(
parser.regex_findall_functions("abc${func(1, 2, a=3, b=4)}def"), parser.regex_findall_functions("abc${func(1, 2, a=3, b=4)}def"),
[('func', '1, 2, a=3, b=4')] [("func", "1, 2, a=3, b=4")],
) )
def test_parse_data_string_with_variables(self): def test_parse_data_string_with_variables(self):
@@ -166,67 +106,35 @@ class TestParserBasic(unittest.TestCase):
"var_3": 123, "var_3": 123,
"var_4": {"a": 1}, "var_4": {"a": 1},
"var_5": True, "var_5": True,
"var_6": None "var_6": None,
} }
self.assertEqual(parser.parse_data("$var_1", variables_mapping), "abc")
self.assertEqual(parser.parse_data("${var_1}", variables_mapping), "abc")
self.assertEqual(parser.parse_data("var_1", variables_mapping), "var_1")
self.assertEqual(parser.parse_data("$var_1#XYZ", variables_mapping), "abc#XYZ")
self.assertEqual( self.assertEqual(
parser.parse_data("$var_1", variables_mapping), parser.parse_data("${var_1}#XYZ", variables_mapping), "abc#XYZ"
"abc"
) )
self.assertEqual( self.assertEqual(
parser.parse_data("${var_1}", variables_mapping), parser.parse_data("/$var_1/$var_2/var3", variables_mapping), "/abc/def/var3"
"abc"
) )
self.assertEqual(parser.parse_data("$var_3", variables_mapping), 123)
self.assertEqual(parser.parse_data("$var_4", variables_mapping), {"a": 1})
self.assertEqual(parser.parse_data("$var_5", variables_mapping), True)
self.assertEqual(parser.parse_data("abc$var_5", variables_mapping), "abcTrue")
self.assertEqual( self.assertEqual(
parser.parse_data("var_1", variables_mapping), parser.parse_data("abc$var_4", variables_mapping), "abc{'a': 1}"
"var_1"
)
self.assertEqual(
parser.parse_data("$var_1#XYZ", variables_mapping),
"abc#XYZ"
)
self.assertEqual(
parser.parse_data("${var_1}#XYZ", variables_mapping),
"abc#XYZ"
)
self.assertEqual(
parser.parse_data("/$var_1/$var_2/var3", variables_mapping),
"/abc/def/var3"
)
self.assertEqual(
parser.parse_data("$var_3", variables_mapping),
123
)
self.assertEqual(
parser.parse_data("$var_4", variables_mapping),
{"a": 1}
)
self.assertEqual(
parser.parse_data("$var_5", variables_mapping),
True
)
self.assertEqual(
parser.parse_data("abc$var_5", variables_mapping),
"abcTrue"
)
self.assertEqual(
parser.parse_data("abc$var_4", variables_mapping),
"abc{'a': 1}"
)
self.assertEqual(
parser.parse_data("$var_6", variables_mapping),
None
) )
self.assertEqual(parser.parse_data("$var_6", variables_mapping), None)
with self.assertRaises(VariableNotFound): with self.assertRaises(VariableNotFound):
parser.parse_data("/api/$SECRET_KEY", variables_mapping) parser.parse_data("/api/$SECRET_KEY", variables_mapping)
self.assertEqual( self.assertEqual(
parser.parse_data(["$var_1", "$var_2"], variables_mapping), parser.parse_data(["$var_1", "$var_2"], variables_mapping), ["abc", "def"]
["abc", "def"]
) )
self.assertEqual( self.assertEqual(
parser.parse_data({"$var_1": "$var_2"}, variables_mapping), parser.parse_data({"$var_1": "$var_2"}, variables_mapping), {"abc": "def"}
{"abc": "def"}
) )
# format: $var # format: $var
@@ -286,52 +194,56 @@ class TestParserBasic(unittest.TestCase):
} }
self.assertEqual( self.assertEqual(
parser.parse_data("/$var_1/$var_2/$var_1", variables_mapping), parser.parse_data("/$var_1/$var_2/$var_1", variables_mapping),
"/abc/def/abc" "/abc/def/abc",
) )
variables_mapping = { variables_mapping = {"userid": 100, "data": 1498}
"userid": 100,
"data": 1498
}
content = "/users/$userid/training/$data?userId=$userid&data=$data" content = "/users/$userid/training/$data?userId=$userid&data=$data"
self.assertEqual( self.assertEqual(
parser.parse_data(content, variables_mapping), parser.parse_data(content, variables_mapping),
"/users/100/training/1498?userId=100&data=1498" "/users/100/training/1498?userId=100&data=1498",
) )
variables_mapping = { variables_mapping = {"user": 100, "userid": 1000, "data": 1498}
"user": 100,
"userid": 1000,
"data": 1498
}
content = "/users/$user/$userid/$data?userId=$userid&data=$data" content = "/users/$user/$userid/$data?userId=$userid&data=$data"
self.assertEqual( self.assertEqual(
parser.parse_data(content, variables_mapping), parser.parse_data(content, variables_mapping),
"/users/100/1000/1498?userId=1000&data=1498" "/users/100/1000/1498?userId=1000&data=1498",
) )
def test_parse_data_string_with_functions(self): def test_parse_data_string_with_functions(self):
import random, string import random, string
functions_mapping = { functions_mapping = {
"gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \ "gen_random_string": lambda str_len: "".join(
for _ in range(str_len)) random.choice(string.ascii_letters + string.digits)
for _ in range(str_len)
)
} }
result = parser.parse_data("${gen_random_string(5)}", functions_mapping=functions_mapping) result = parser.parse_data(
"${gen_random_string(5)}", functions_mapping=functions_mapping
)
self.assertEqual(len(result), 5) self.assertEqual(len(result), 5)
add_two_nums = lambda a, b=1: a + b add_two_nums = lambda a, b=1: a + b
functions_mapping["add_two_nums"] = add_two_nums functions_mapping["add_two_nums"] = add_two_nums
self.assertEqual( self.assertEqual(
parser.parse_data("${add_two_nums(1)}", functions_mapping=functions_mapping), parser.parse_data(
2 "${add_two_nums(1)}", functions_mapping=functions_mapping
),
2,
) )
self.assertEqual( self.assertEqual(
parser.parse_data("${add_two_nums(1, 2)}", functions_mapping=functions_mapping), parser.parse_data(
3 "${add_two_nums(1, 2)}", functions_mapping=functions_mapping
),
3,
) )
self.assertEqual( self.assertEqual(
parser.parse_data("/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping), parser.parse_data(
"/api/3" "/api/${add_two_nums(1, 2)}", functions_mapping=functions_mapping
),
"/api/3",
) )
with self.assertRaises(FunctionNotFound): with self.assertRaises(FunctionNotFound):
@@ -343,28 +255,38 @@ class TestParserBasic(unittest.TestCase):
"var_3": 123, "var_3": 123,
"var_4": {"a": 1}, "var_4": {"a": 1},
"var_5": True, "var_5": True,
"var_6": None "var_6": None,
}
functions_mapping = {
"func1": lambda x, y: str(x) + str(y)
} }
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
value = parser.parse_data("${func1($var_1, $var_3)}", variables_mapping, functions_mapping) value = parser.parse_data(
"${func1($var_1, $var_3)}", variables_mapping, functions_mapping
)
self.assertEqual(value, "abc123") self.assertEqual(value, "abc123")
value = parser.parse_data("ABC${func1($var_1, $var_3)}DE", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC${func1($var_1, $var_3)}DE", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABCabc123DE") self.assertEqual(value, "ABCabc123DE")
value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_5", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC${func1($var_1, $var_3)}$var_5", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABCabc123True") self.assertEqual(value, "ABCabc123True")
value = parser.parse_data("ABC${func1($var_1, $var_3)}DE$var_4", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC${func1($var_1, $var_3)}DE$var_4", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABCabc123DE{'a': 1}") self.assertEqual(value, "ABCabc123DE{'a': 1}")
value = parser.parse_data("ABC$var_5${func1($var_1, $var_3)}", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC$var_5${func1($var_1, $var_3)}", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABCTrueabc123") self.assertEqual(value, "ABCTrueabc123")
value = parser.parse_data("ABC${ord(a)}DEF${len(abcd)}", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC${ord(a)}DEF${len(abcd)}", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABC97DEF4") self.assertEqual(value, "ABC97DEF4")
def test_parse_data_func_var_duplicate(self): def test_parse_data_func_var_duplicate(self):
@@ -374,22 +296,26 @@ class TestParserBasic(unittest.TestCase):
"var_3": 123, "var_3": 123,
"var_4": {"a": 1}, "var_4": {"a": 1},
"var_5": True, "var_5": True,
"var_6": None "var_6": None,
}
functions_mapping = {
"func1": lambda x, y: str(x) + str(y)
} }
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
value = parser.parse_data( value = parser.parse_data(
"ABC${func1($var_1, $var_3)}--${func1($var_1, $var_3)}", "ABC${func1($var_1, $var_3)}--${func1($var_1, $var_3)}",
variables_mapping, functions_mapping) variables_mapping,
functions_mapping,
)
self.assertEqual(value, "ABCabc123--abc123") self.assertEqual(value, "ABCabc123--abc123")
value = parser.parse_data("ABC${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping) value = parser.parse_data(
"ABC${func1($var_1, $var_3)}$var_1", variables_mapping, functions_mapping
)
self.assertEqual(value, "ABCabc123abc") self.assertEqual(value, "ABCabc123abc")
value = parser.parse_data( value = parser.parse_data(
"ABC${func1($var_1, $var_3)}$var_1--${func1($var_1, $var_3)}$var_1", "ABC${func1($var_1, $var_3)}$var_1--${func1($var_1, $var_3)}$var_1",
variables_mapping, functions_mapping) variables_mapping,
functions_mapping,
)
self.assertEqual(value, "ABCabc123abc--abc123abc") self.assertEqual(value, "ABCabc123abc--abc123abc")
def test_parse_data_func_abnormal(self): def test_parse_data_func_abnormal(self):
@@ -399,20 +325,22 @@ class TestParserBasic(unittest.TestCase):
"var_3": 123, "var_3": 123,
"var_4": {"a": 1}, "var_4": {"a": 1},
"var_5": True, "var_5": True,
"var_6": None "var_6": None,
}
functions_mapping = {
"func1": lambda x, y: str(x) + str(y)
} }
functions_mapping = {"func1": lambda x, y: str(x) + str(y)}
# { # {
value = parser.parse_data("ABC$var_1{", variables_mapping, functions_mapping) value = parser.parse_data("ABC$var_1{", variables_mapping, functions_mapping)
self.assertEqual(value, "ABCabc{") self.assertEqual(value, "ABCabc{")
value = parser.parse_data("{ABC$var_1{}a}", variables_mapping, functions_mapping) value = parser.parse_data(
"{ABC$var_1{}a}", variables_mapping, functions_mapping
)
self.assertEqual(value, "{ABCabc{}a}") self.assertEqual(value, "{ABCabc{}a}")
value = parser.parse_data("AB{C$var_1{}a}", variables_mapping, functions_mapping) value = parser.parse_data(
"AB{C$var_1{}a}", variables_mapping, functions_mapping
)
self.assertEqual(value, "AB{Cabc{}a}") self.assertEqual(value, "AB{Cabc{}a}")
# } # }
@@ -452,27 +380,21 @@ class TestParserBasic(unittest.TestCase):
def test_parse_data_request(self): def test_parse_data_request(self):
content = { content = {
'request': { "request": {
'url': '/api/users/$uid', "url": "/api/users/$uid",
'method': "$method", "method": "$method",
'headers': {'token': '$token'}, "headers": {"token": "$token"},
'data': { "data": {
"null": None, "null": None,
"true": True, "true": True,
"false": False, "false": False,
"empty_str": "", "empty_str": "",
"value": "abc${add_one(3)}def" "value": "abc${add_one(3)}def",
},
} }
} }
} variables_mapping = {"uid": 1000, "method": "POST", "token": "abc123"}
variables_mapping = { functions_mapping = {"add_one": lambda x: x + 1}
"uid": 1000,
"method": "POST",
"token": "abc123"
}
functions_mapping = {
"add_one": lambda x: x + 1
}
result = parser.parse_data(content, variables_mapping, functions_mapping) result = parser.parse_data(content, variables_mapping, functions_mapping)
self.assertEqual("/api/users/1000", result["request"]["url"]) self.assertEqual("/api/users/1000", result["request"]["url"])
self.assertEqual("abc123", result["request"]["headers"]["token"]) self.assertEqual("abc123", result["request"]["headers"]["token"])
@@ -488,11 +410,11 @@ class TestParserBasic(unittest.TestCase):
"uid": "1000", "uid": "1000",
"random": "A2dEx", "random": "A2dEx",
"authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d",
"data": {"name": "user", "password": "123456"} "data": {"name": "user", "password": "123456"},
} }
functions = { functions = {
"add_two_nums": lambda a, b=1: a + b, "add_two_nums": lambda a, b=1: a + b,
"get_timestamp": lambda: int(time.time() * 1000) "get_timestamp": lambda: int(time.time() * 1000),
} }
testcase_template = { testcase_template = {
"url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}", "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}",
@@ -501,28 +423,17 @@ class TestParserBasic(unittest.TestCase):
"Content-Type": "application/json", "Content-Type": "application/json",
"authorization": "$authorization", "authorization": "$authorization",
"random": "$random", "random": "$random",
"sum": "${add_two_nums(1, 2)}" "sum": "${add_two_nums(1, 2)}",
}, },
"body": "$data" "body": "$data",
} }
parsed_testcase = parser.parse_data(testcase_template, variables, functions) parsed_testcase = parser.parse_data(testcase_template, variables, functions)
self.assertEqual( self.assertEqual(
parsed_testcase["url"], parsed_testcase["url"], "http://127.0.0.1:5000/api/users/1000/3"
"http://127.0.0.1:5000/api/users/1000/3"
) )
self.assertEqual( self.assertEqual(
parsed_testcase["headers"]["authorization"], parsed_testcase["headers"]["authorization"], variables["authorization"]
variables["authorization"]
)
self.assertEqual(
parsed_testcase["headers"]["random"],
variables["random"]
)
self.assertEqual(
parsed_testcase["body"],
variables["data"]
)
self.assertEqual(
parsed_testcase["headers"]["sum"],
3
) )
self.assertEqual(parsed_testcase["headers"]["random"], variables["random"])
self.assertEqual(parsed_testcase["body"], variables["data"])
self.assertEqual(parsed_testcase["headers"]["sum"], 3)
+1 -1
View File
@@ -16,5 +16,5 @@ __all__ = [
"get_summary", "get_summary",
"stringify_summary", "stringify_summary",
"HtmlTestResult", "HtmlTestResult",
"gen_html_report" "gen_html_report",
] ]
+1 -4
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"
]
+13 -9
View File
@@ -8,7 +8,12 @@ from httprunner.exceptions import SummaryEmpty
from httprunner.schema import TestSuiteSummary from httprunner.schema import TestSuiteSummary
def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, report_dir=None, report_file=None): def gen_html_report(
testsuite_summary: TestSuiteSummary,
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:
@@ -24,8 +29,7 @@ def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, r
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.debug("No html report template specified, use default.") logger.debug("No html report template specified, use default.")
else: else:
@@ -39,22 +43,22 @@ def gen_html_report(testsuite_summary: TestSuiteSummary, report_template=None, r
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(testsuite_summary.time.start_at_iso_format.replace(":", "").replace("-", "")) report_file_name = "{}.html".format(
testsuite_summary.time.start_at_iso_format.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(testsuite_summary.dict()) ).render(testsuite_summary.dict())
fp_w.write(rendered_content) fp_w.write(rendered_content)
logger.info(f"Generated Html report: {report_path}") logger.info(f"Generated Html report: {report_path}")
return report_path return report_path
+8 -7
View File
@@ -8,6 +8,7 @@ class HtmlTestResult(unittest.TextTestResult):
""" A html result class that can generate formatted html results, used by TextTestRunner. """ A html result class that can generate formatted html results, used by TextTestRunner.
Each testcase is corresponding to one HtmlTestResult instance Each testcase is corresponding to one HtmlTestResult instance
""" """
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.name = "" self.name = ""
@@ -15,7 +16,7 @@ class HtmlTestResult(unittest.TextTestResult):
self.attachment = "" self.attachment = ""
self.step_datas = None self.step_datas = None
def _record_test(self, test, status, attachment=''): def _record_test(self, test, status, attachment=""):
self.name = test.shortDescription() self.name = test.shortDescription()
self.status = status self.status = status
self.attachment = attachment self.attachment = attachment
@@ -31,32 +32,32 @@ class HtmlTestResult(unittest.TextTestResult):
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
+1 -1
View File
@@ -210,7 +210,7 @@
<div class="content"> <div class="content">
<h3>Name: {{ session_data.name }}</h3> <h3>Name: {{ session_data.name }}</h3>
{% for req_resp in session_data.req_resp %} {% for req_resp in session_data.req_resps %}
{% if loop.index > 1 %} {% if loop.index > 1 %}
<div class="separator">==================================== redirect to ====================================</div> <div class="separator">==================================== redirect to ====================================</div>
+8 -14
View File
@@ -12,40 +12,34 @@ 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")
+18 -7
View File
@@ -1,11 +1,12 @@
import json import json
from base64 import b64encode from base64 import b64encode
from collections import Iterable from collections import Iterable
from typing import List
from jinja2 import escape from jinja2 import escape
from requests.cookies import RequestsCookieJar from requests.cookies import RequestsCookieJar
from httprunner.schema import TestSuiteSummary from httprunner.schema import TestSuiteSummary, SessionData
def dumps_json(value): def dumps_json(value):
@@ -125,8 +126,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))
@@ -152,8 +152,19 @@ def stringify_summary(testsuite_summary: TestSuiteSummary):
testcase_summary.name = f"testcase {index}" testcase_summary.name = f"testcase {index}"
step_datas = testcase_summary.step_datas step_datas = testcase_summary.step_datas
for session_data in step_datas: for step_data in step_datas:
req_resp_list = session_data.req_resp if isinstance(step_data.data, list):
# List[SessionData]
session_data_list: List[SessionData] = step_data.data
for session_data in session_data_list:
req_resp_list = session_data.req_resps
for req_resp in req_resp_list: for req_resp in req_resp_list:
__stringify_request(req_resp["request"]) __stringify_request(req_resp.request)
__stringify_response(req_resp["response"]) __stringify_response(req_resp.response)
else:
# SessionData
session_data: SessionData = step_data.data
req_resp_list = session_data.req_resps
for req_resp in req_resp_list:
__stringify_request(req_resp.request)
__stringify_response(req_resp.response)
+8 -7
View File
@@ -10,10 +10,9 @@ 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(),
} }
@@ -33,8 +32,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:
@@ -65,11 +66,11 @@ def get_summary(result: HtmlTestResult) -> TestCaseSummary:
time=TestCaseTime( time=TestCaseTime(
start_at=result.start_at, start_at=result.start_at,
start_at_iso_format=start_at_iso_format, start_at_iso_format=start_at_iso_format,
duration=result.duration duration=result.duration,
), ),
name=result.name, name=result.name,
status=result.status, status=result.status,
attachment=result.attachment, attachment=result.attachment,
in_out=TestCaseInOut(), in_out=TestCaseInOut(),
step_datas=result.step_datas step_datas=result.step_datas,
) )
+31 -19
View File
@@ -28,15 +28,28 @@ def get_uniform_comparator(comparator: Text):
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
@@ -90,15 +103,10 @@ def uniform_validator(validator):
# uniform comparator, e.g. lt => less_than, eq => equals # uniform comparator, e.g. lt => less_than, eq => equals
assert_method = get_uniform_comparator(comparator) assert_method = get_uniform_comparator(comparator)
return { return {"check": check_item, "expect": expect_value, "assert": assert_method}
"check": check_item,
"expect": expect_value,
"assert": assert_method
}
class ResponseObject(object): class ResponseObject(object):
def __init__(self, resp_obj: requests.Response): def __init__(self, resp_obj: requests.Response):
""" initialize with a requests.Response object """ initialize with a requests.Response object
@@ -110,7 +118,7 @@ class ResponseObject(object):
self.resp_obj_meta = { self.resp_obj_meta = {
"status_code": resp_obj.status_code, "status_code": resp_obj.status_code,
"headers": resp_obj.headers, "headers": resp_obj.headers,
"body": resp_obj.json() "body": resp_obj.json(),
} }
self.validation_results: Dict = {} self.validation_results: Dict = {}
@@ -126,10 +134,12 @@ class ResponseObject(object):
logger.info(f"extract mapping: {extract_mapping}") logger.info(f"extract mapping: {extract_mapping}")
return extract_mapping return extract_mapping
def validate(self, def validate(
self,
validators: Validators, validators: Validators,
variables_mapping: VariablesMapping = None, variables_mapping: VariablesMapping = None,
functions_mapping: FunctionsMapping = None) -> NoReturn: functions_mapping: FunctionsMapping = None,
) -> NoReturn:
self.validation_results = {} self.validation_results = {}
if not validators: if not validators:
@@ -166,7 +176,7 @@ class ResponseObject(object):
"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,
} }
try: try:
@@ -178,11 +188,13 @@ class ResponseObject(object):
validate_pass = False validate_pass = False
validator_dict["check_result"] = "fail" validator_dict["check_result"] = "fail"
validate_msg += "\t==> fail" validate_msg += "\t==> fail"
validate_msg += f"\n" \ validate_msg += (
f"check_item: {check_item}\n" \ f"\n"
f"check_value: {check_value}({type(check_value).__name__})\n" \ f"check_item: {check_item}\n"
f"assert_method: {assert_method}\n" \ f"check_value: {check_value}({type(check_value).__name__})\n"
f"assert_method: {assert_method}\n"
f"expect_value: {expect_value}({type(expect_value).__name__})" f"expect_value: {expect_value}({type(expect_value).__name__})"
)
logger.error(validate_msg) logger.error(validate_msg)
failures.append(validate_msg) failures.append(validate_msg)
+20 -11
View File
@@ -7,7 +7,13 @@ from httprunner.client import HttpSession
from httprunner.exceptions import ValidationFailure, ParamsError from httprunner.exceptions import ValidationFailure, ParamsError
from httprunner.parser import build_url, parse_data, parse_variables_mapping from httprunner.parser import build_url, parse_data, parse_variables_mapping
from httprunner.response import ResponseObject from httprunner.response import ResponseObject
from httprunner.schema import TestsConfig, TestStep, VariablesMapping, TestCase, StepData from httprunner.schema import (
TestsConfig,
TestStep,
VariablesMapping,
TestCase,
StepData,
)
class TestCaseRunner(object): class TestCaseRunner(object):
@@ -35,13 +41,13 @@ class TestCaseRunner(object):
def __run_step_request(self, step: TestStep): def __run_step_request(self, step: TestStep):
"""run teststep: request""" """run teststep: request"""
step_data = StepData( step_data = StepData(name=step.name)
name=step.name
)
# parse # parse
request_dict = step.request.dict() request_dict = step.request.dict()
parsed_request_dict = parse_data(request_dict, step.variables, self.config.functions) parsed_request_dict = parse_data(
request_dict, step.variables, self.config.functions
)
# prepare arguments # prepare arguments
method = parsed_request_dict.pop("method") method = parsed_request_dict.pop("method")
@@ -109,9 +115,7 @@ class TestCaseRunner(object):
def __run_step_testcase(self, step): def __run_step_testcase(self, step):
"""run teststep: referenced testcase""" """run teststep: referenced testcase"""
step_data = StepData( step_data = StepData(name=step.name)
name=step.name
)
step_variables = step.variables step_variables = step.variables
testcase: TestCaseRunner = step.testcase() # TODO: fix testcase: TestCaseRunner = step.testcase() # TODO: fix
case_result = testcase.with_variables(**step_variables).run() case_result = testcase.with_variables(**step_variables).run()
@@ -131,7 +135,9 @@ class TestCaseRunner(object):
elif step.testcase: elif step.testcase:
step_data = self.__run_step_testcase(step) step_data = self.__run_step_testcase(step)
else: else:
raise ParamsError(f"teststep is neither a request nor a referenced testcase: {step.dict()}") raise ParamsError(
f"teststep is neither a request nor a referenced testcase: {step.dict()}"
)
self.step_datas.append(step_data) self.step_datas.append(step_data)
return step_data.export return step_data.export
@@ -146,7 +152,9 @@ class TestCaseRunner(object):
# update with session variables extracted from former step # update with session variables extracted from former step
step.variables.update(self.session_variables) step.variables.update(self.session_variables)
# parse variables # parse variables
step.variables = parse_variables_mapping(step.variables, self.config.functions) step.variables = parse_variables_mapping(
step.variables, self.config.functions
)
# run step # run step
extract_mapping = self.__run_step(step) extract_mapping = self.__run_step(step)
# save extracted variables to session variables # save extracted variables to session variables
@@ -163,7 +171,8 @@ class TestCaseRunner(object):
for var_name in self.config.export: for var_name in self.config.export:
if var_name not in self.session_variables: if var_name not in self.session_variables:
raise ParamsError( raise ParamsError(
f"failed to export variable {var_name} from session variables {self.session_variables}") f"failed to export variable {var_name} from session variables {self.session_variables}"
)
export_vars_mapping[var_name] = self.session_variables[var_name] export_vars_mapping[var_name] = self.session_variables[var_name]
+5 -2
View File
@@ -20,8 +20,8 @@ Env = Dict[Text, Any]
class MethodEnum(Text, Enum): class MethodEnum(Text, Enum):
GET = 'GET' GET = "GET"
POST = 'POST' POST = "POST"
PUT = "PUT" PUT = "PUT"
DELETE = "DELETE" DELETE = "DELETE"
HEAD = "HEAD" HEAD = "HEAD"
@@ -44,6 +44,7 @@ class TestsConfig(BaseModel):
class Request(BaseModel): class Request(BaseModel):
"""requests.Request model""" """requests.Request model"""
method: MethodEnum = MethodEnum.GET method: MethodEnum = MethodEnum.GET
url: Url url: Url
params: Dict[Text, Text] = {} params: Dict[Text, Text] = {}
@@ -125,6 +126,7 @@ class ReqRespData(BaseModel):
class SessionData(BaseModel): class SessionData(BaseModel):
"""request session data, including request, response, validators and stat data""" """request session data, including request, response, validators and stat data"""
success: bool = False success: bool = False
# in most cases, req_resps only contains one request & response # in most cases, req_resps only contains one request & response
# while when 30X redirect occurs, req_resps will contain multiple request & response # while when 30X redirect occurs, req_resps will contain multiple request & response
@@ -135,6 +137,7 @@ class SessionData(BaseModel):
class StepData(BaseModel): class StepData(BaseModel):
"""teststep data, each step maybe corresponding to one request or one testcase""" """teststep data, each step maybe corresponding to one request or one testcase"""
success: bool = False success: bool = False
name: Text = "" # teststep name name: Text = "" # teststep name
data: Union[SessionData, List[SessionData]] = None data: Union[SessionData, List[SessionData]] = None
+8 -8
View File
@@ -80,10 +80,7 @@ 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 deepcopy_dict(data): def deepcopy_dict(data):
@@ -230,6 +227,7 @@ def omit_long_data(body, omit_len=512):
def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> None: def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> None:
""" 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:
@@ -242,14 +240,14 @@ def dump_json_file(json_data: Union[dict, list], json_file_abs_path: str) -> Non
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:
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 = f"dump file: {json_file_abs_path}" msg = f"dump file: {json_file_abs_path}"
@@ -268,7 +266,9 @@ def prepare_log_file_abs_path(test_path: str, file_name: str) -> str:
if not test_path: if not test_path:
# running passed in testcase/testsuite data structure # running passed in testcase/testsuite data structure
dump_file_name = f"tests_mapping.{file_name}" dump_file_name = f"tests_mapping.{file_name}"
dumped_json_file_abs_path = os.path.join(current_working_dir, "logs", dump_file_name) dumped_json_file_abs_path = os.path.join(
current_working_dir, "logs", dump_file_name
)
return dumped_json_file_abs_path return dumped_json_file_abs_path
# both test_path and pwd_dir_path are absolute path # both test_path and pwd_dir_path are absolute path
+27 -61
View File
@@ -6,18 +6,16 @@ from httprunner import loader, utils
class TestUtils(unittest.TestCase): class TestUtils(unittest.TestCase):
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")
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)
@@ -37,15 +35,15 @@ class TestUtils(unittest.TestCase):
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$")
@@ -72,10 +70,7 @@ class TestUtils(unittest.TestCase):
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)
@@ -93,64 +88,43 @@ class TestUtils(unittest.TestCase):
def test_deepcopy_dict(self): def test_deepcopy_dict(self):
license_path = os.path.join( license_path = os.path.join(
os.path.dirname(os.path.dirname(__file__)), os.path.dirname(os.path.dirname(__file__)), "LICENSE"
"LICENSE"
) )
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_path), "d": open(license_path),
'f': { "f": {"f1": {"a1": 2}, "f2": io.open(license_path, "rb"),},
'f1': {'a1': 2},
'f2': io.open(license_path, '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"]))
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):
@@ -159,15 +133,7 @@ class TestUtils(unittest.TestCase):
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):
@@ -175,7 +141,7 @@ class TestUtils(unittest.TestCase):
test_path = os.path.join("tests", "httpbin", "a.b.c") test_path = os.path.join("tests", "httpbin", "a.b.c")
self.assertEqual( self.assertEqual(
utils.prepare_log_file_abs_path(test_path, "loaded.json"), utils.prepare_log_file_abs_path(test_path, "loaded.json"),
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/all.loaded.json") os.path.join(os.getcwd(), "logs", "tests/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):
@@ -183,12 +149,12 @@ class TestUtils(unittest.TestCase):
test_path = os.path.join("tests", "httpbin", "a.b.c", "rpc.yml") test_path = os.path.join("tests", "httpbin", "a.b.c", "rpc.yml")
self.assertEqual( self.assertEqual(
utils.prepare_log_file_abs_path(test_path, "loaded.json"), utils.prepare_log_file_abs_path(test_path, "loaded.json"),
os.path.join(os.getcwd(), "logs", "tests/httpbin/a.b.c/rpc.loaded.json") os.path.join(os.getcwd(), "logs", "tests/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):
test_path = "" test_path = ""
self.assertEqual( self.assertEqual(
utils.prepare_log_file_abs_path(test_path, "loaded.json"), utils.prepare_log_file_abs_path(test_path, "loaded.json"),
os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json") os.path.join(os.getcwd(), "logs", "tests_mapping.loaded.json"),
) )