diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b36a1022..16044001 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,10 @@ ## 3.0.6 (2020-05-23) +**Added** + +- feat: make referenced testcase as pytest class + **Fixed** - fix: ensure converted python file in utf-8 encoding diff --git a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py index 9a714755..7b9d121b 100644 --- a/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference_test.py @@ -2,6 +2,10 @@ # FROM: examples/postman_echo/request_methods/demo_testsuite_yml/request_with_testcase_reference.yml from httprunner import HttpRunner, TConfig, TStep +from examples.postman_echo.request_methods.request_with_functions_test import ( + TestCaseRequestWithFunctions, +) + class TestCaseRequestWithTestcaseReference(HttpRunner): config = TConfig( @@ -19,7 +23,7 @@ class TestCaseRequestWithTestcaseReference(HttpRunner): **{ "name": "request with functions", "variables": {"foo1": "override_bar1"}, - "testcase": "request_methods/request_with_functions.yml", + "testcase": TestCaseRequestWithFunctions, } ), ] diff --git a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py index 229c04fc..ea3921c1 100644 --- a/examples/postman_echo/request_methods/request_with_testcase_reference_test.py +++ b/examples/postman_echo/request_methods/request_with_testcase_reference_test.py @@ -2,6 +2,10 @@ # FROM: examples/postman_echo/request_methods/request_with_testcase_reference.yml from httprunner import HttpRunner, TConfig, TStep +from examples.postman_echo.request_methods.request_with_functions_test import ( + TestCaseRequestWithFunctions, +) + class TestCaseRequestWithTestcaseReference(HttpRunner): config = TConfig( @@ -19,7 +23,7 @@ class TestCaseRequestWithTestcaseReference(HttpRunner): **{ "name": "request with functions", "variables": {"foo1": "override_bar1"}, - "testcase": "request_methods/request_with_functions.yml", + "testcase": TestCaseRequestWithFunctions, } ), ] diff --git a/httprunner/ext/make/__init__.py b/httprunner/ext/make/__init__.py index a1611579..44b9f370 100644 --- a/httprunner/ext/make/__init__.py +++ b/httprunner/ext/make/__init__.py @@ -18,6 +18,9 @@ from httprunner.parser import parse_data __TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT! # FROM: {{ testcase_path }} from httprunner import HttpRunner, TConfig, TStep +{% for import_str in imports_list %} +{{ import_str }} +{% endfor %} class {{ class_name }}(HttpRunner): @@ -55,8 +58,9 @@ def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]: # convert title case, e.g. request_with_variables => RequestWithVariables name_in_title_case = file_name.title().replace("_", "") + testcase_cls_name = f"TestCase{name_in_title_case}" - return testcase_python_path, name_in_title_case + return testcase_python_path, testcase_cls_name def format_pytest_with_black(python_paths: List[Text]): @@ -81,10 +85,15 @@ def make_testcase(testcase: Dict) -> Union[str, None]: template = jinja2.Template(__TMPL__) - testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path) + # convert abs path to relative + if os.path.isabs(testcase_path): + testcase_path = testcase_path[len(os.getcwd()) + 1 :] + testcase_python_path, testcase_cls_name = convert_testcase_path(testcase_path) config = testcase["config"] + config["path"] = testcase_python_path + # parse config variables config.setdefault("variables", {}) if isinstance(config["variables"], Text): # get variables by function, e.g. ${get_variables()} @@ -93,14 +102,41 @@ def make_testcase(testcase: Dict) -> Union[str, None]: config["variables"], {}, project_meta.functions ) - config["path"] = testcase_python_path + # prepare reference testcase + imports_list = [] + teststeps = testcase["teststeps"] + for teststep in teststeps: + if not teststep.get("testcase"): + continue + + ref_testcase_path = teststep["testcase"] + + # make ref testcase pytest file + project_meta = load_project_meta(testcase_path) + ref_testcase_path = os.path.join(project_meta.PWD, ref_testcase_path) + __make(ref_testcase_path) + + # prepare ref testcase class name + ref_testcase_python_path, ref_testcase_cls_name = convert_testcase_path( + ref_testcase_path + ) + teststep["testcase"] = f"CLS_LB({ref_testcase_cls_name})CLS_RB" + + # prepare import ref testcase + ref_testcase_python_path = ref_testcase_python_path[len(os.getcwd()) + 1 :] + ref_module_name, _ = os.path.splitext(ref_testcase_python_path) + ref_module_name = ref_module_name.replace(os.sep, ".") + imports_list.append(f"from {ref_module_name} import {ref_testcase_cls_name}") + data = { "testcase_path": testcase_path, - "class_name": f"TestCase{name_in_title_case}", + "class_name": testcase_cls_name, "config": config, - "teststeps": testcase["teststeps"], + "teststeps": teststeps, + "imports_list": imports_list, } content = template.render(data) + content = content.replace("'CLS_LB(", "").replace(")CLS_RB'", "") with open(testcase_python_path, "w", encoding="utf-8") as f: f.write(content) diff --git a/httprunner/ext/make/make_test.py b/httprunner/ext/make/make_test.py index 7145807c..0405cf08 100644 --- a/httprunner/ext/make/make_test.py +++ b/httprunner/ext/make/make_test.py @@ -12,6 +12,25 @@ class TestLoader(unittest.TestCase): "examples/postman_echo/request_methods/request_with_variables_test.py", ) + def test_make_testcase_with_ref(self): + path = [ + "examples/postman_echo/request_methods/request_with_testcase_reference.yml" + ] + testcase_python_list = main_make(path) + with open(testcase_python_list[0]) as f: + content = f.read() + self.assertIn( + """ +from examples.postman_echo.request_methods.request_with_functions_test import ( + TestCaseRequestWithFunctions, +) +""", + content, + ) + self.assertIn( + '"testcase": TestCaseRequestWithFunctions,', content, + ) + def test_make_testcase_folder(self): path = ["examples/postman_echo/request_methods/"] testcase_python_list = main_make(path) @@ -33,26 +52,28 @@ class TestLoader(unittest.TestCase): "/path/to 2/mubu_login_test.py", ) self.assertEqual( - convert_testcase_path("/path/to 2/mubu.login.yml")[1], "MubuLogin" + convert_testcase_path("/path/to 2/mubu.login.yml")[1], "TestCaseMubuLogin" ) self.assertEqual( convert_testcase_path("mubu login.yml")[0], "mubu_login_test.py" ) self.assertEqual( - convert_testcase_path("/path/to 2/mubu login.yml")[1], "MubuLogin" + convert_testcase_path("/path/to 2/mubu login.yml")[1], "TestCaseMubuLogin" ) self.assertEqual( convert_testcase_path("/path/to 2/mubu-login.yml")[0], "/path/to 2/mubu_login_test.py", ) self.assertEqual( - convert_testcase_path("/path/to 2/mubu-login.yml")[1], "MubuLogin" + convert_testcase_path("/path/to 2/mubu-login.yml")[1], "TestCaseMubuLogin" ) self.assertEqual( convert_testcase_path("/path/to 2/幕布login.yml")[0], "/path/to 2/幕布login_test.py", ) - self.assertEqual(convert_testcase_path("/path/to/幕布login.yml")[1], "幕布Login") + self.assertEqual( + convert_testcase_path("/path/to/幕布login.yml")[1], "TestCase幕布Login" + ) def test_make_testsuite(self): path = ["examples/postman_echo/request_methods/demo_testsuite.yml"] diff --git a/httprunner/ext/uploader/__init__.py b/httprunner/ext/uploader/__init__.py index b051f6ee..88a246f6 100644 --- a/httprunner/ext/uploader/__init__.py +++ b/httprunner/ext/uploader/__init__.py @@ -125,6 +125,7 @@ def multipart_encoder(**kwargs) -> MultipartEncoder: else: # value is not absolute file path, check if it is relative file path from httprunner.loader import load_project_meta + project_meta = load_project_meta(os.getcwd()) _file_path = os.path.join(project_meta.PWD, value) diff --git a/httprunner/runner.py b/httprunner/runner.py index a97c4f89..5a890f82 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -141,14 +141,35 @@ class HttpRunner(object): step_data = StepData(name=step.name) step_variables = step.variables - ref_testcase_path = os.path.join(self.__project_meta.PWD, step.testcase) - case_result = ( - HttpRunner() - .with_session(self.__session) - .with_case_id(self.__case_id) - .with_variables(step_variables) - .run_path(ref_testcase_path) - ) + if hasattr(step.testcase, "config") and hasattr(step.testcase, "teststeps"): + testcase_cls = step.testcase + case_result = ( + testcase_cls() + .with_session(self.__session) + .with_case_id(self.__case_id) + .with_variables(step_variables) + .run() + ) + + elif isinstance(step.testcase, Text): + if os.path.isabs(step.testcase): + ref_testcase_path = step.testcase + else: + ref_testcase_path = os.path.join(self.__project_meta.PWD, step.testcase) + + case_result = ( + HttpRunner() + .with_session(self.__session) + .with_case_id(self.__case_id) + .with_variables(step_variables) + .run_path(ref_testcase_path) + ) + + else: + raise exceptions.ParamsError( + f"Invalid teststep referenced testcase: {step}" + ) + step_data.data = case_result.get_step_datas() # list of step data step_data.export = case_result.get_export_variables() step_data.success = case_result.success @@ -186,7 +207,13 @@ class HttpRunner(object): ) def run_testcase(self, testcase: TestCase): - """run testcase""" + """run specified testcase + + Examples: + >>> testcase_obj = TestCase(config=TConfig(...), teststeps=[TStep(...)]) + >>> HttpRunner().with_project_meta(project_meta).run_testcase(testcase_obj) + + """ self.config = testcase.config self.teststeps = testcase.teststeps @@ -224,6 +251,16 @@ class HttpRunner(object): testcase_obj = load_testcase_file(path) return self.run_testcase(testcase_obj) + def run(self) -> "HttpRunner": + """ run current testcase + + Examples: + >>> TestCaseRequestWithFunctions().run() + + """ + testcase_obj = TestCase(config=self.config, teststeps=self.teststeps) + return self.run_testcase(testcase_obj) + def get_step_datas(self) -> List[StepData]: return self.__step_datas @@ -284,7 +321,9 @@ class HttpRunner(object): ) try: - return self.run_testcase(TestCase(config=self.config, teststeps=self.teststeps)) + return self.run_testcase( + TestCase(config=self.config, teststeps=self.teststeps) + ) finally: logger.remove(log_handler) logger.info(f"generate testcase log: {self.__log_path}") diff --git a/httprunner/schema.py b/httprunner/schema.py index c6c0fe97..90211c42 100644 --- a/httprunner/schema.py +++ b/httprunner/schema.py @@ -64,7 +64,7 @@ class Request(BaseModel): class TStep(BaseModel): name: Name request: Request = None - testcase: Text = "" + testcase: Union[Text, Callable] = "" variables: VariablesMapping = {} extract: Dict[Text, Text] = {} validators: Validators = Field([], alias="validate")