From 74299a5a5c5a8848c426b342f3406322307d29b2 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sat, 23 Apr 2022 15:04:35 +0800 Subject: [PATCH] change: remove testsuite --- httprunner/loader.py | 45 +++++--------- httprunner/make.py | 129 +++++++++++----------------------------- httprunner/make_test.py | 59 ++++-------------- httprunner/models.py | 29 +++------ 4 files changed, 71 insertions(+), 191 deletions(-) diff --git a/httprunner/loader.py b/httprunner/loader.py index 6c81fd82..49dc849a 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -11,14 +11,13 @@ from loguru import logger from pydantic import ValidationError from httprunner import builtin, exceptions, utils -from httprunner.models import ProjectMeta, TestCase, TestSuite +from httprunner.models import ProjectMeta, TestCase project_meta: Union[ProjectMeta, None] = None def _load_yaml_file(yaml_file: Text) -> Dict: - """ load yaml file and check file content format - """ + """load yaml file and check file content format""" with open(yaml_file, mode="rb") as stream: try: yaml_content = yaml.load(stream, Loader=yaml.FullLoader) @@ -31,8 +30,7 @@ def _load_yaml_file(yaml_file: Text) -> Dict: def _load_json_file(json_file: Text) -> Dict: - """ load json file and check file content format - """ + """load json file and check file content format""" with open(json_file, mode="rb") as data_file: try: json_content = json.load(data_file) @@ -81,20 +79,8 @@ def load_testcase_file(testcase_file: Text) -> TestCase: return testcase_obj -def load_testsuite(testsuite: Dict) -> TestSuite: - path = testsuite["config"]["path"] - try: - # validate with pydantic TestCase model - testsuite_obj = TestSuite.parse_obj(testsuite) - except ValidationError as ex: - err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}" - raise exceptions.TestSuiteFormatError(err_msg) - - return testsuite_obj - - def load_dot_env_file(dot_env_path: Text) -> Dict: - """ load .env file. + """load .env file. Args: dot_env_path (str): .env file path @@ -140,7 +126,7 @@ def load_dot_env_file(dot_env_path: Text) -> Dict: def load_csv_file(csv_file: Text) -> List[Dict]: - """ load csv file and check file content format + """load csv file and check file content format Args: csv_file (str): csv file path, csv file content is like below: @@ -186,7 +172,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]: def load_folder_files(folder_path: Text, recursive: bool = True) -> List: - """ load folder path, return all files endswith .yml/.yaml/.json/_test.py in list. + """load folder path, return all files endswith .yml/.yaml/.json/_test.py in list. Args: folder_path (str): specified folder path to load @@ -227,7 +213,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List: def load_module_functions(module) -> Dict[Text, Callable]: - """ load python module functions. + """load python module functions. Args: module: python module @@ -251,13 +237,12 @@ def load_module_functions(module) -> Dict[Text, Callable]: def load_builtin_functions() -> Dict[Text, Callable]: - """ load builtin module functions - """ + """load builtin module functions""" return load_module_functions(builtin) def locate_file(start_path: Text, file_name: Text) -> Text: - """ locate filename and return absolute file path. + """locate filename and return absolute file path. searching will be recursive upward until system root dir. Args: @@ -295,7 +280,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text: def locate_debugtalk_py(start_path: Text) -> Text: - """ locate debugtalk.py file + """locate debugtalk.py file Args: start_path (str): start locating path, @@ -315,7 +300,7 @@ def locate_debugtalk_py(start_path: Text) -> Text: def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]: - """ locate debugtalk.py path as project root directory + """locate debugtalk.py path as project root directory Args: test_path: specified testfile path @@ -352,7 +337,7 @@ def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]: def load_debugtalk_functions() -> Dict[Text, Callable]: - """ load project debugtalk.py module functions + """load project debugtalk.py module functions debugtalk.py should be located in project root directory. Returns: @@ -376,7 +361,7 @@ def load_debugtalk_functions() -> Dict[Text, Callable]: def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta: - """ load testcases, .env, debugtalk.py functions. + """load testcases, .env, debugtalk.py functions. testcases folder is relative to project_root_directory by default, project_meta will be loaded only once, unless set reload to true. @@ -428,7 +413,7 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta: def convert_relative_project_root_dir(abs_path: Text) -> Text: - """ convert absolute path to relative path, based on project_meta.RootDir + """convert absolute path to relative path, based on project_meta.RootDir Args: abs_path: absolute path @@ -444,4 +429,4 @@ def convert_relative_project_root_dir(abs_path: Text) -> Text: f"project_meta.RootDir: {_project_meta.RootDir}" ) - return abs_path[len(_project_meta.RootDir) + 1:] + return abs_path[len(_project_meta.RootDir) + 1 :] diff --git a/httprunner/make.py b/httprunner/make.py index 744b61bd..962bb30a 100644 --- a/httprunner/make.py +++ b/httprunner/make.py @@ -8,14 +8,21 @@ import jinja2 from loguru import logger from httprunner import __version__, exceptions -from httprunner.compat import (convert_variables, ensure_path_sep, - ensure_testcase_v3, ensure_testcase_v3_api) -from httprunner.loader import (convert_relative_project_root_dir, - load_folder_files, load_project_meta, - load_test_file, load_testcase, load_testsuite) +from httprunner.compat import ( + convert_variables, + ensure_path_sep, + ensure_testcase_v3, + ensure_testcase_v3_api, +) +from httprunner.loader import ( + convert_relative_project_root_dir, + load_folder_files, + load_project_meta, + load_test_file, + load_testcase, +) from httprunner.response import uniform_validator -from httprunner.utils import (ga_client, is_support_multiprocessing, - merge_variables) +from httprunner.utils import ga_client, is_support_multiprocessing """ cache converted pytest files, avoid duplicate making """ @@ -71,10 +78,10 @@ if __name__ == "__main__": def __ensure_absolute(path: Text) -> Text: if path.startswith("./"): # Linux/Darwin, hrun ./test.yml - path = path[len("./"):] + path = path[len("./") :] elif path.startswith(".\\"): # Windows, hrun .\\test.yml - path = path[len(".\\"):] + path = path[len(".\\") :] path = ensure_path_sep(path) project_meta = load_project_meta(path) @@ -92,7 +99,7 @@ def __ensure_absolute(path: Text) -> Text: def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: - """ ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space + """ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space Args: file_abs_path: absolute file path @@ -133,8 +140,7 @@ def ensure_file_abs_path_valid(file_abs_path: Text) -> Text: def __ensure_testcase_module(path: Text): - """ ensure pytest files are in python module, generate __init__.py on demand - """ + """ensure pytest files are in python module, generate __init__.py on demand""" init_file = os.path.join(os.path.dirname(path), "__init__.py") if os.path.isfile(init_file): return @@ -431,61 +437,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text: return testcase_python_abs_path -def make_testsuite(testsuite: Dict): - """convert valid testsuite dict to pytest folder with testcases""" - # validate testsuite format - load_testsuite(testsuite) - - testsuite_config = testsuite["config"] - testsuite_path = testsuite_config["path"] - testsuite_variables = convert_variables( - testsuite_config.get("variables", {}), testsuite_path - ) - - logger.info(f"start to make testsuite: {testsuite_path}") - - # create directory with testsuite file name, put its testcases under this directory - testsuite_path = ensure_file_abs_path_valid(testsuite_path) - testsuite_dir, file_suffix = os.path.splitext(testsuite_path) - # demo_testsuite.yml => demo_testsuite_yml - testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}" - - for testcase in testsuite["testcases"]: - # get referenced testcase content - testcase_file = testcase["testcase"] - testcase_path = __ensure_absolute(testcase_file) - testcase_dict = load_test_file(testcase_path) - testcase_dict.setdefault("config", {}) - testcase_dict["config"]["path"] = testcase_path - - # override testcase name - testcase_dict["config"]["name"] = testcase["name"] - # override base_url - base_url = testsuite_config.get("base_url") or testcase.get("base_url") - if base_url: - testcase_dict["config"]["base_url"] = base_url - # override verify - if "verify" in testsuite_config: - testcase_dict["config"]["verify"] = testsuite_config["verify"] - # override variables - # testsuite testcase variables > testsuite config variables - testcase_variables = convert_variables( - testcase.get("variables", {}), testcase_path - ) - testcase_variables = merge_variables(testcase_variables, testsuite_variables) - # testsuite testcase variables > testcase config variables - testcase_dict["config"]["variables"] = convert_variables( - testcase_dict["config"].get("variables", {}), testcase_path - ) - testcase_dict["config"]["variables"].update(testcase_variables) - - # make testcase - testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir) - pytest_files_run_set.add(testcase_pytest_path) - - def __make(tests_path: Text): - """ make testcase(s) with testcase/testsuite/folder absolute path + """make testcase(s) with testcase/folder absolute path generated pytest file path will be cached in pytest_files_made_cache_mapping Args: @@ -526,13 +479,13 @@ def __make(tests_path: Text): if "config" not in test_content: logger.warning( - f"Invalid testcase/testsuite file: {test_file}\n" + f"Invalid testcase file: {test_file}\n" f"reason: missing config part." ) continue elif not isinstance(test_content["config"], Dict): logger.warning( - f"Invalid testcase/testsuite file: {test_file}\n" + f"Invalid testcase file: {test_file}\n" f"reason: config should be dict type, got {test_content['config']}" ) continue @@ -540,33 +493,19 @@ def __make(tests_path: Text): # ensure path absolute test_content.setdefault("config", {})["path"] = test_file - # testcase - if "teststeps" in test_content: - try: - testcase_pytest_path = make_testcase(test_content) - pytest_files_run_set.add(testcase_pytest_path) - except exceptions.TestCaseFormatError as ex: - logger.warning( - f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}" - ) - continue - - # testsuite - elif "testcases" in test_content: - try: - make_testsuite(test_content) - except exceptions.TestSuiteFormatError as ex: - logger.warning( - f"Invalid testsuite file: {test_file}\n{type(ex).__name__}: {ex}" - ) - continue - # invalid format - else: + if "teststeps" not in test_content: + logger.warning(f"Invalid testcase file: {test_file}") + + # testcase + try: + testcase_pytest_path = make_testcase(test_content) + pytest_files_run_set.add(testcase_pytest_path) + except exceptions.TestCaseFormatError as ex: logger.warning( - f"Invalid test file: {test_file}\n" - f"reason: file content is neither testcase nor testsuite" + f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}" ) + continue def main_make(tests_paths: List[Text]) -> List[Text]: @@ -594,10 +533,10 @@ def main_make(tests_paths: List[Text]) -> List[Text]: def init_make_parser(subparsers): - """ make testcases: parse command line options and run commands. - """ + """make testcases: parse command line options and run commands.""" parser = subparsers.add_parser( - "make", help="Convert YAML/JSON testcases to pytest cases.", + "make", + help="Convert YAML/JSON testcases to pytest cases.", ) parser.add_argument( "testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path" diff --git a/httprunner/make_test.py b/httprunner/make_test.py index b6a40f20..f3a80325 100644 --- a/httprunner/make_test.py +++ b/httprunner/make_test.py @@ -73,7 +73,8 @@ from request_methods.request_with_functions_test import ( content, ) self.assertIn( - ".call(RequestWithFunctions)", content, + ".call(RequestWithFunctions)", + content, ) def test_make_testcase_folder(self): @@ -94,9 +95,7 @@ from request_methods.request_with_functions_test import ( def test_ensure_file_path_valid(self): self.assertEqual( - ensure_file_abs_path_valid( - os.path.join(self.data_dir, "a-b.c", "2 3.yml") - ), + ensure_file_abs_path_valid(os.path.join(self.data_dir, "a-b.c", "2 3.yml")), os.path.join(self.data_dir, "a_b_c", "T2_3.yml"), ) loader.project_meta = None @@ -113,67 +112,31 @@ from request_methods.request_with_functions_test import ( ) loader.project_meta = None self.assertEqual( - ensure_file_abs_path_valid(os.getcwd()), os.getcwd(), + ensure_file_abs_path_valid(os.getcwd()), + os.getcwd(), ) loader.project_meta = None self.assertEqual( - ensure_file_abs_path_valid( - os.path.join(self.data_dir, ".csv") - ), + ensure_file_abs_path_valid(os.path.join(self.data_dir, ".csv")), os.path.join(self.data_dir, ".csv"), ) def test_convert_testcase_path(self): self.assertEqual( - convert_testcase_path( - os.path.join(self.data_dir, "a-b.c", "2 3.yml") - ), + convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "2 3.yml")), ( os.path.join(self.data_dir, "a_b_c", "T2_3_test.py"), "T23", ), ) self.assertEqual( - convert_testcase_path( - os.path.join(self.data_dir, "a-b.c", "中文case.yml") - ), + convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "中文case.yml")), ( os.path.join(self.data_dir, "a_b_c", "中文case_test.py"), "中文Case", ), ) - def test_make_testsuite(self): - path = ["examples/postman_echo/request_methods/demo_testsuite.yml"] - testcase_python_list = main_make(path) - self.assertEqual(len(testcase_python_list), 2) - self.assertIn( - os.path.join( - os.getcwd(), - os.path.join( - "examples", - "postman_echo", - "request_methods", - "demo_testsuite_yml", - "request_with_functions_test.py", - ), - ), - testcase_python_list, - ) - self.assertIn( - os.path.join( - os.getcwd(), - os.path.join( - "examples", - "postman_echo", - "request_methods", - "demo_testsuite_yml", - "request_with_testcase_reference_test.py", - ), - ), - testcase_python_list, - ) - def test_make_config_chain_style(self): config = { "name": "request methods testcase: validate with functions", @@ -190,7 +153,11 @@ from request_methods.request_with_functions_test import ( def test_make_teststep_chain_style(self): step = { "name": "get with params", - "variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",}, + "variables": { + "foo1": "bar1", + "foo2": 123, + "sum_v": "${sum_two(1, 2)}", + }, "request": { "method": "GET", "url": "/get", diff --git a/httprunner/models.py b/httprunner/models.py index 8c8ced42..d43a3a94 100644 --- a/httprunner/models.py +++ b/httprunner/models.py @@ -97,7 +97,9 @@ class ProjectMeta(BaseModel): dot_env_path: Text = "" # .env file path functions: FunctionsMapping = {} # functions defined in debugtalk.py env: Env = {} - RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located + RootDir: Text = ( + os.getcwd() + ) # project root directory (ensure absolute), the path debugtalk.py located class TestsMapping(BaseModel): @@ -166,21 +168,20 @@ class SessionData(BaseModel): class StepResult(BaseModel): """teststep data, each step maybe corresponding to one request or one testcase""" - name: Text = "" # teststep name - step_type: Text = "" # teststep type, request or testcase + name: Text = "" # teststep name + step_type: Text = "" # teststep type, request or testcase success: bool = False - data: Union[SessionData, List['StepResult']] = None - elapsed: float = 0.0 # teststep elapsed time - content_size: float = 0 # response content size + data: Union[SessionData, List["StepResult"]] = None + elapsed: float = 0.0 # teststep elapsed time + content_size: float = 0 # response content size export_vars: VariablesMapping = {} - attachment: Text = "" # teststep attachment + attachment: Text = "" # teststep attachment StepResult.update_forward_refs() class IStep(object): - def name(self) -> str: raise NotImplementedError @@ -211,18 +212,6 @@ class PlatformInfo(BaseModel): platform: Text -class TestCaseRef(BaseModel): - name: Text - base_url: Text = "" - testcase: Text - variables: VariablesMapping = {} - - -class TestSuite(BaseModel): - config: TConfig - testcases: List[TestCaseRef] - - class Stat(BaseModel): total: int = 0 success: int = 0