diff --git a/examples/postman_echo/request_methods/request_with_variables_test.py b/examples/postman_echo/request_methods/request_with_variables_test.py index 5456b681..9fdfbcd4 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -2,89 +2,81 @@ from httprunner.runner import TestCaseRunner from httprunner.schema import TestsConfig, TestStep -class TestCaseRequestMethodsWithVariables(TestCaseRunner): - config = TestsConfig(**{ - "name": "request methods testcase with variables", - "variables": { - "foo1": "session_bar1" - }, - "base_url": "https://postman-echo.com", - "verify": False - }) +class TestCaseRequestWithVariables(TestCaseRunner): + config = TestsConfig( + **{ + "name": "request methods testcase with variables", + "variables": {"foo1": "session_bar1"}, + "base_url": "https://postman-echo.com", + "verify": False, + } + ) teststeps = [ - TestStep(**{ - "name": "get with params", - "variables": { - "foo1": "bar1", - "foo2": "session_bar2" - }, - "request": { - "method": "GET", - "url": "/get", - "params": { - "foo1": "$foo1", - "foo2": "$foo2" + TestStep( + **{ + "name": "get with params", + "variables": {"foo1": "bar1", "foo2": "session_bar2"}, + "request": { + "method": "GET", + "url": "/get", + "params": {"foo1": "$foo1", "foo2": "$foo2"}, + "headers": {"User-Agent": "HttpRunner/3.0"}, }, - "headers": { - "User-Agent": "HttpRunner/3.0" - } - }, - "extract": { - "session_foo2": "body.args.foo2" - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.args.foo1", "session_bar1"]}, - {"eq": ["body.args.foo2", "session_bar2"]} - ] - }), - TestStep(**{ - "name": "post raw text", - "variables": { - "foo1": "hello world", - "foo3": "$session_foo2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "This is expected to be sent back as part of response body: $foo1-$foo3.", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "text/plain" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": [ - "body.data", - "This is expected to be sent back as part of response body: session_bar1-session_bar2." - ]}, - ] - }), - TestStep(**{ - "name": "post form data", - "variables": { - "foo1": "session_bar1", - "foo2": "bar2" - }, - "request": { - "method": "POST", - "url": "/post", - "data": "foo1=$foo1&foo2=$foo2", - "headers": { - "User-Agent": "HttpRunner/3.0", - "Content-Type": "application/x-www-form-urlencoded" - } - }, - "validate": [ - {"eq": ["status_code", 200]}, - {"eq": ["body.form.foo1", "session_bar1"]}, - {"eq": ["body.form.foo2", "bar2"]} - ] - }) + "extract": {"session_foo2": "body.args.foo2"}, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["body.args.foo1", "session_bar1"]}, + {"eq": ["body.args.foo2", "session_bar2"]}, + ], + } + ), + TestStep( + **{ + "name": "post raw text", + "variables": {"foo1": "hello world", "foo3": "$session_foo2"}, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "text/plain", + }, + "data": "This is expected to be sent back as part of response body: $foo1-$foo3.", + }, + "validate": [ + {"eq": ["status_code", 200]}, + { + "eq": [ + "body.data", + "This is expected to be sent back as part of response body: session_bar1-session_bar2.", + ] + }, + ], + } + ), + TestStep( + **{ + "name": "post form data", + "variables": {"foo1": "bar1", "foo2": "bar2"}, + "request": { + "method": "POST", + "url": "/post", + "headers": { + "User-Agent": "HttpRunner/3.0", + "Content-Type": "application/x-www-form-urlencoded", + }, + "data": "foo1=$foo1&foo2=$foo2", + }, + "validate": [ + {"eq": ["status_code", 200]}, + {"eq": ["body.form.foo1", "session_bar1"]}, + {"eq": ["body.form.foo2", "bar2"]}, + ], + } + ), ] -if __name__ == '__main__': - TestCaseRequestMethodsWithVariables().run() +if __name__ == "__main__": + TestCaseRequestWithVariables().run() diff --git a/httprunner/loader_test.py b/httprunner/loader_test.py new file mode 100644 index 00000000..e66d47ad --- /dev/null +++ b/httprunner/loader_test.py @@ -0,0 +1,11 @@ +import unittest +from httprunner.new_loader import load_testcase_file + + +class TestLoader(unittest.TestCase): + + def test_load_testcase_file(self): + path = "examples/postman_echo/request_methods/request_with_variables.yml" + testcase = load_testcase_file(path) + self.assertEqual(testcase.config.name, "request methods testcase with variables") + self.assertEqual(len(testcase.teststeps), 3) diff --git a/httprunner/make.py b/httprunner/make.py new file mode 100644 index 00000000..0e8d838d --- /dev/null +++ b/httprunner/make.py @@ -0,0 +1,49 @@ +import os + +import jinja2 +from loguru import logger + +from httprunner.new_loader import load_testcase_file + +__TMPL__ = """ +from httprunner.runner import TestCaseRunner +from httprunner.schema import TestsConfig, TestStep + + +class {{ class_name }}(TestCaseRunner): + config = TestsConfig(**{{ config }}) + + teststeps = [ + {% for teststep in teststeps %} + TestStep(**{{ teststep }}), + {% endfor %} + ] + + +if __name__ == '__main__': + {{ class_name }}().run() +""" + + +def make_testcase(path: str) -> str: + testcase = load_testcase_file(path) + template = jinja2.Template(__TMPL__) + + raw_file_name, _ = os.path.splitext(os.path.basename(path)) + # convert title case, e.g. request_with_variables => RequestWithVariables + name_in_title_case = raw_file_name.title().replace("_", "") + + data = { + "class_name": f"TestCase{name_in_title_case}", + "config": testcase["config"], + "teststeps": testcase["teststeps"], + } + content = template.render(data) + + testcase_dir = os.path.dirname(path) + testcase_python_path = os.path.join(testcase_dir, f"{raw_file_name}_test.py") + with open(testcase_python_path, "w") as f: + f.write(content) + + logger.info(f"generated testcase: {testcase_python_path}") + return testcase_python_path diff --git a/httprunner/new_loader.py b/httprunner/new_loader.py new file mode 100644 index 00000000..f7efc1cc --- /dev/null +++ b/httprunner/new_loader.py @@ -0,0 +1,135 @@ +import io +import json +import os +import types + +import yaml +from loguru import logger + +from httprunner import builtin +from httprunner import exceptions +from httprunner.schema import TestCase + +try: + # PyYAML version >= 5.1 + # ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation + yaml.warnings({"YAMLLoadWarning": False}) +except AttributeError: + pass + + +def _load_yaml_file(yaml_file): + """ load yaml file and check file content format + """ + with io.open(yaml_file, "r", encoding="utf-8") as stream: + try: + yaml_content = yaml.load(stream) + except yaml.YAMLError as ex: + logger.error(str(ex)) + raise exceptions.FileFormatError + + return yaml_content + + +def _load_json_file(json_file): + """ load json file and check file content format + """ + with io.open(json_file, encoding="utf-8") as data_file: + try: + json_content = json.load(data_file) + except json.JSONDecodeError: + err_msg = f"JSONDecodeError: JSON file format error: {json_file}" + logger.error(err_msg) + raise exceptions.FileFormatError(err_msg) + + return json_content + + +def load_testcase_file(testcase_file): + """load testcase file and validate with pydantic model""" + file_suffix = os.path.splitext(testcase_file)[1].lower() + if file_suffix == ".json": + testcase_content = _load_json_file(testcase_file) + elif file_suffix in [".yaml", ".yml"]: + testcase_content = _load_yaml_file(testcase_file) + else: + # '' or other suffix + raise exceptions.FileFormatError( + f"testcase file should be YAML/JSON format, invalid testcase file: {testcase_file}" + ) + + # validate with pydantic TestCase model + TestCase.parse_obj(testcase_content) + + return testcase_content + + +def load_folder_files(folder_path, recursive=True): + """ load folder path, return all files endswith yml/yaml/json in list. + + Args: + folder_path (str): specified folder path to load + recursive (bool): load files recursively if True + + Returns: + list: files endswith yml/yaml/json + """ + if isinstance(folder_path, (list, set)): + files = [] + for path in set(folder_path): + files.extend(load_folder_files(path, recursive)) + + return files + + if not os.path.exists(folder_path): + return [] + + file_list = [] + + for dirpath, dirnames, filenames in os.walk(folder_path): + filenames_list = [] + + for filename in filenames: + if not filename.endswith((".yml", ".yaml", ".json")): + continue + + filenames_list.append(filename) + + for filename in filenames_list: + file_path = os.path.join(dirpath, filename) + file_list.append(file_path) + + if not recursive: + break + + return file_list + + +def load_module_functions(module): + """ load python module functions. + + Args: + module: python module + + Returns: + dict: functions mapping for specified python module + + { + "func1_name": func1, + "func2_name": func2 + } + + """ + module_functions = {} + + for name, item in vars(module).items(): + if isinstance(item, types.FunctionType): + module_functions[name] = item + + return module_functions + + +def load_builtin_functions(): + """ load builtin module functions + """ + return load_module_functions(builtin)