From 2cd9eba6a3245258427e8fd2c80e41e50873e47c Mon Sep 17 00:00:00 2001 From: debugtalk Date: Fri, 15 May 2020 20:05:17 +0800 Subject: [PATCH] refactor: use HttpRunner as testcase base class --- .../request_methods/hardcode_test.py | 9 +- .../request_with_functions_test.py | 9 +- .../request_with_testcase_reference_test.py | 9 +- .../request_with_variables_test.py | 9 +- .../validate_with_functions_test.py | 9 +- .../validate_with_variables_test.py | 9 +- httprunner/__init__.py | 2 - httprunner/api.py | 278 ------------------ httprunner/api_test.py | 28 -- httprunner/ext/make/__init__.py | 8 +- httprunner/runner.py | 64 ++-- httprunner/runner_test.py | 0 12 files changed, 71 insertions(+), 363 deletions(-) delete mode 100644 httprunner/api.py delete mode 100644 httprunner/api_test.py create mode 100644 httprunner/runner_test.py diff --git a/examples/postman_echo/request_methods/hardcode_test.py b/examples/postman_echo/request_methods/hardcode_test.py index e66af98f..bfbe315b 100644 --- a/examples/postman_echo/request_methods/hardcode_test.py +++ b/examples/postman_echo/request_methods/hardcode_test.py @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseHardcode(TestCase): +class TestCaseHardcode(HttpRunner): config = TConfig( **{ "name": "request methods testcase in hardcode", @@ -72,5 +72,6 @@ class TestCaseHardcode(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseHardcode().test_start() diff --git a/examples/postman_echo/request_methods/request_with_functions_test.py b/examples/postman_echo/request_methods/request_with_functions_test.py index 68da74d9..ebe586dd 100644 --- a/examples/postman_echo/request_methods/request_with_functions_test.py +++ b/examples/postman_echo/request_methods/request_with_functions_test.py @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseRequestWithFunctions(TestCase): +class TestCaseRequestWithFunctions(HttpRunner): config = TConfig( **{ "name": "request methods testcase with functions", @@ -83,5 +83,6 @@ class TestCaseRequestWithFunctions(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseRequestWithFunctions().test_start() 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 bf81cd95..40a59f8f 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 @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseRequestWithTestcaseReference(TestCase): +class TestCaseRequestWithTestcaseReference(HttpRunner): config = TConfig( **{ "name": "request methods testcase: reference testcase", @@ -23,5 +23,6 @@ class TestCaseRequestWithTestcaseReference(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseRequestWithTestcaseReference().test_start() 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 4a8a1ef6..4edb4932 100644 --- a/examples/postman_echo/request_methods/request_with_variables_test.py +++ b/examples/postman_echo/request_methods/request_with_variables_test.py @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseRequestWithVariables(TestCase): +class TestCaseRequestWithVariables(HttpRunner): config = TConfig( **{ "name": "request methods testcase with variables", @@ -78,5 +78,6 @@ class TestCaseRequestWithVariables(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseRequestWithVariables().test_start() diff --git a/examples/postman_echo/request_methods/validate_with_functions_test.py b/examples/postman_echo/request_methods/validate_with_functions_test.py index 4f66ca47..160c1390 100644 --- a/examples/postman_echo/request_methods/validate_with_functions_test.py +++ b/examples/postman_echo/request_methods/validate_with_functions_test.py @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseValidateWithFunctions(TestCase): +class TestCaseValidateWithFunctions(HttpRunner): config = TConfig( **{ "name": "request methods testcase: validate with functions", @@ -38,5 +38,6 @@ class TestCaseValidateWithFunctions(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseValidateWithFunctions().test_start() diff --git a/examples/postman_echo/request_methods/validate_with_variables_test.py b/examples/postman_echo/request_methods/validate_with_variables_test.py index 72cd5140..2bb44eb0 100644 --- a/examples/postman_echo/request_methods/validate_with_variables_test.py +++ b/examples/postman_echo/request_methods/validate_with_variables_test.py @@ -1,8 +1,8 @@ # NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class TestCaseValidateWithVariables(TestCase): +class TestCaseValidateWithVariables(HttpRunner): config = TConfig( **{ "name": "request methods testcase: validate with variables", @@ -78,5 +78,6 @@ class TestCaseValidateWithVariables(TestCase): ), ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() + +if __name__ == "__main__": + TestCaseValidateWithVariables().test_start() diff --git a/httprunner/__init__.py b/httprunner/__init__.py index abec38e2..37b5c190 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,7 +1,6 @@ __version__ = "3.0.2" __description__ = "One-stop solution for HTTP(S) testing." -from unittest import TestCase from httprunner.runner import HttpRunner from httprunner.schema import TConfig, TStep @@ -11,5 +10,4 @@ __all__ = [ "HttpRunner", "TConfig", "TStep", - "TestCase", ] diff --git a/httprunner/api.py b/httprunner/api.py deleted file mode 100644 index dabae5a1..00000000 --- a/httprunner/api.py +++ /dev/null @@ -1,278 +0,0 @@ -import os -import sys -import unittest -from typing import List - -from loguru import logger - -from httprunner import report, loader, utils, exceptions, __version__ -from httprunner.report import gen_html_report -from httprunner.runner import HttpRunner as TestCaseRunner -from httprunner.schema import TestsMapping, TestCaseSummary, TestSuiteSummary - - -class HttpRunner(object): - """ Developer Interface: Main Interface - Usage: - - from httprunner.api import HttpRunner - runner = HttpRunner( - failfast=True, - save_tests=True, - log_level="INFO", - log_file="test.log" - ) - summary = runner.run(path_or_tests) - - """ - - def __init__(self, save_tests=False, log_level="WARNING", log_file=None): - """ initialize HttpRunner. - - Args: - save_tests (bool): save loaded/parsed tests to JSON file. - log_level (str): logging level. - log_file (str): log file path. - - """ - self.exception_stage = "initialize HttpRunner()" - kwargs = {"failfast": True, "resultclass": report.HtmlTestResult} - - logger.remove() - log_level = log_level.upper() - logger.add(sys.stdout, level=log_level) - if log_file: - logger.add(log_file, level=log_level) - - self.unittest_runner = unittest.TextTestRunner(**kwargs) - self.test_loader = unittest.TestLoader() - self.save_tests = save_tests - self._summary = None - self.test_path = None - - def _prepare_tests(self, tests: TestsMapping) -> List[unittest.TestSuite]: - def _add_test(test_runner: TestCaseRunner): - """ add test to testcase. - """ - - def test(self): - try: - test_runner.run() - except exceptions.MyBaseFailure as ex: - self.fail(str(ex)) - finally: - self.step_datas = test_runner.step_datas - - test.__doc__ = test_runner.config.name - return test - - project_meta = tests.project_meta - testcases = tests.testcases - - prepared_testcases: List[unittest.TestSuite] = [] - - for testcase in testcases: - test_runner = TestCaseRunner(testcase.config, testcase.teststeps) - - TestSequense = type("TestSequense", (unittest.TestCase,), {}) - test_method = _add_test(test_runner) - setattr(TestSequense, "test_method_name", test_method) - - loaded_testcase = self.test_loader.loadTestsFromTestCase(TestSequense) - setattr(loaded_testcase, "config", testcase.config) - prepared_testcases.append(loaded_testcase) - - return prepared_testcases - - def _run_suite( - self, prepared_testcases: List[unittest.TestSuite] - ) -> List[TestCaseSummary]: - """ run prepared testcases - """ - tests_results: List[TestCaseSummary] = [] - - for index, testcase in enumerate(prepared_testcases): - log_handler = None - if self.save_tests: - logs_file_abs_path = utils.prepare_log_file_abs_path( - self.test_path, f"testcase_{index+1}.log" - ) - log_handler = logger.add(logs_file_abs_path, level="DEBUG") - - logger.info(f"Start to run testcase: {testcase.config.name}") - - result = self.unittest_runner.run(testcase) - testcase_summary = report.get_summary(result) - testcase_summary.in_out.vars = testcase.config.variables - testcase_summary.in_out.export = testcase.config.export - - if self.save_tests and log_handler: - logger.remove(log_handler) - logs_file_abs_path = utils.prepare_log_file_abs_path( - self.test_path, f"testcase_{index+1}.log" - ) - testcase_summary.log = logs_file_abs_path - - if result.wasSuccessful(): - tests_results.append(testcase_summary) - else: - tests_results.insert(0, testcase_summary) - - return tests_results - - def _aggregate(self, tests_results: List[TestCaseSummary]) -> TestSuiteSummary: - """ aggregate multiple testcase results - - Args: - tests_results (list): list of testcase summary - - """ - testsuite_summary = TestSuiteSummary( - success=True, platform=report.get_platform(), testcases=[] - ) - testsuite_summary.stat.total = len(tests_results) - testsuite_summary.stat.success = 0 - testsuite_summary.stat.fail = 0 - - for testcase_summary in tests_results: - if testcase_summary.success: - testsuite_summary.stat.success += 1 - else: - testsuite_summary.stat.fail += 1 - - testsuite_summary.success &= testcase_summary.success - testsuite_summary.testcases.append(testcase_summary) - - total_duration = ( - tests_results[-1].time.start_at - + tests_results[-1].time.duration - - 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.duration = total_duration - - return testsuite_summary - - def run_tests(self, tests_mapping) -> TestSuiteSummary: - """ run testcase/testsuite data - """ - tests = TestsMapping.parse_obj(tests_mapping) - self.test_path = tests.project_meta.test_path - - if self.save_tests: - utils.dump_json_file( - tests_mapping, - utils.prepare_log_file_abs_path(self.test_path, "loaded.json"), - ) - - # prepare testcases - self.exception_stage = "prepare testcases" - prepared_testcases = self._prepare_tests(tests) - - # run prepared testcases - self.exception_stage = "run prepared testcases" - results = self._run_suite(prepared_testcases) - - # aggregate results - self.exception_stage = "aggregate results" - self._summary = self._aggregate(results) - - # generate html report - self.exception_stage = "generate html report" - - if self.save_tests: - utils.dump_json_file( - self._summary.dict(), - utils.prepare_log_file_abs_path(self.test_path, "summary.json"), - ) - # save variables and export data - vars_out = self.get_vars_out() - utils.dump_json_file( - vars_out, utils.prepare_log_file_abs_path(self.test_path, "io.json") - ) - - return self._summary - - def get_vars_out(self): - """ get variables and output - Returns: - list: list of variables and output. - if tests are parameterized, list items are corresponded to parameters. - - [ - { - "in": { - "user1": "leo" - }, - "out": { - "out1": "out_value_1" - } - }, - {...} - ] - - None: returns None if tests not started or finished or corrupted. - - """ - if not self._summary: - return None - - return [ - testcase_summary.in_out.dict() - for testcase_summary in self._summary.testcases - ] - - def run_path(self, path, dot_env_path=None, mapping=None) -> TestSuiteSummary: - """ run testcase/testsuite file or folder. - - Args: - path (str): testcase/testsuite file/foler path. - dot_env_path (str): specified .env file path. - mapping (dict): if mapping is specified, it will override variables in config block. - - Returns: - dict: result summary - - """ - # load tests - logger.info(f"HttpRunner version: {__version__}") - self.exception_stage = "load tests" - tests_mapping = loader.load_cases(path, dot_env_path) - - if mapping: - tests_mapping["project_meta"]["variables"] = mapping - - return self.run_tests(tests_mapping) - - def run(self, path_or_tests, dot_env_path=None, mapping=None): - """ main interface. - - Args: - path_or_tests: - str: testcase/testsuite file/foler path - dict: valid testcase/testsuite data - dot_env_path (str): specified .env file path. - mapping (dict): if mapping is specified, it will override variables in config block. - - Returns: - dict: result summary - - """ - if loader.is_test_path(path_or_tests): - return self.run_path(path_or_tests, dot_env_path, mapping) - - project_working_directory = path_or_tests.get("project_meta", {}).get( - "PWD", os.getcwd() - ) - loader.init_pwd(project_working_directory) - return self.run_tests(path_or_tests) - - def gen_html_report(self, report_template=None, report_dir=None, report_file=None): - if not self._summary: - return None - - return gen_html_report(self._summary, report_template, report_dir, report_file) diff --git a/httprunner/api_test.py b/httprunner/api_test.py deleted file mode 100644 index b99bff16..00000000 --- a/httprunner/api_test.py +++ /dev/null @@ -1,28 +0,0 @@ -import unittest - -from httprunner.api import HttpRunner - - -class TestHttpRunner(unittest.TestCase): - def setUp(self): - self.runner = HttpRunner() - - def test_run_testcase_by_path_request_only(self): - summary = self.runner.run_path( - "examples/postman_echo/request_methods/request_with_variables.yml" - ) - self.assertTrue(summary.success) - 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) diff --git a/httprunner/ext/make/__init__.py b/httprunner/ext/make/__init__.py index 70456cd7..bebbc762 100644 --- a/httprunner/ext/make/__init__.py +++ b/httprunner/ext/make/__init__.py @@ -10,10 +10,10 @@ from httprunner.exceptions import TestCaseFormatError from httprunner.loader import load_testcase_file, load_folder_files __TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT! -from httprunner import HttpRunner, TConfig, TStep, TestCase +from httprunner import HttpRunner, TConfig, TStep -class {{ class_name }}(TestCase): +class {{ class_name }}(HttpRunner): config = TConfig(**{{ config }}) teststeps = [ @@ -22,8 +22,8 @@ class {{ class_name }}(TestCase): {% endfor %} ] - def test_start(self): - HttpRunner(self.config, self.teststeps).run() +if __name__ == "__main__": + {{ class_name }}().test_start() """ diff --git a/httprunner/runner.py b/httprunner/runner.py index ec6505af..5561c812 100644 --- a/httprunner/runner.py +++ b/httprunner/runner.py @@ -5,7 +5,7 @@ from typing import List, Dict from loguru import logger -from httprunner import utils, exceptions +from httprunner import utils from httprunner.client import HttpSession from httprunner.exceptions import ValidationFailure, ParamsError from httprunner.loader import load_project_meta, load_testcase_file @@ -20,38 +20,34 @@ from httprunner.schema import ( TestCaseTime, TestCaseInOut, ProjectMeta, + TestCase, ) class HttpRunner(object): - def __init__( - self, config: TConfig, teststeps: List[TStep], session: HttpSession = None, - ): - if not config.path: - raise exceptions.ParamsError("config path missed!") + config: TConfig + teststeps: List[TStep] - self.config = config - self.teststeps = teststeps - self.session = session - self.step_datas: List[StepData] = [] - self.validation_results: Dict = {} - self.session_variables: Dict = {} - self.success: bool = True # indicate testcase execution result - - if self.config.path: - self.project_meta = load_project_meta(self.config.path) - else: - self.project_meta = ProjectMeta() - - self.start_at = 0 - self.duration = 0 + session: HttpSession + variables: VariablesMapping = {} + step_datas: List[StepData] = [] + validation_results: Dict = {} + session_variables: Dict = {} + success: bool = True # indicate testcase execution result + project_meta: ProjectMeta = None + start_at = 0 + duration = 0 def with_project_meta(self, project_meta: ProjectMeta) -> "HttpRunner": self.project_meta = project_meta return self - def with_variables(self, **variables: VariablesMapping) -> "HttpRunner": - self.config.variables.update(variables) + def with_session(self, session: HttpSession) -> "HttpRunner": + self.session = session + return self + + def with_variables(self, variables: VariablesMapping) -> "HttpRunner": + self.variables = variables return self def __run_step_request(self, step: TStep): @@ -139,9 +135,10 @@ class HttpRunner(object): _, testcase_obj = load_testcase_file(ref_testcase_path) case_result = ( - HttpRunner(testcase_obj.config, testcase_obj.teststeps, self.session) - .with_variables(**step_variables) - .run() + HttpRunner() + .with_session(self.session) + .with_variables(step_variables) + .run(testcase_obj) ) step_data.data = case_result.step_datas # list of step data step_data.export = case_result.get_export_variables() @@ -166,8 +163,17 @@ class HttpRunner(object): self.step_datas.append(step_data) return step_data.export - def run(self): + def run(self, testcase: TestCase): """main entrance""" + self.config = testcase.config + self.teststeps = testcase.teststeps + self.config.variables.update(self.variables) + + if self.config.path: + self.project_meta = load_project_meta(self.config.path) + else: + self.project_meta = ProjectMeta() + self.start_at = time.time() self.step_datas.clear() self.session_variables.clear() @@ -219,3 +225,7 @@ class HttpRunner(object): ), step_datas=self.step_datas, ) + + def test_start(self): + """discovered by pytest""" + self.run(TestCase(config=self.config, teststeps=self.teststeps)) diff --git a/httprunner/runner_test.py b/httprunner/runner_test.py new file mode 100644 index 00000000..e69de29b