diff --git a/httprunner/__about__.py b/httprunner/__about__.py index 0c141353..e44d2253 100644 --- a/httprunner/__about__.py +++ b/httprunner/__about__.py @@ -1,7 +1,7 @@ __title__ = 'HttpRunner' __description__ = 'One-stop solution for HTTP(S) testing.' __url__ = 'https://github.com/HttpRunner/HttpRunner' -__version__ = '1.3.8.beta.3' +__version__ = '1.3.9' __author__ = 'debugtalk' __author_email__ = 'mail@debugtalk.com' __license__ = 'MIT' diff --git a/httprunner/cli.py b/httprunner/cli.py index cf026a6a..0e609932 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -26,6 +26,9 @@ def main_hrun(): parser.add_argument( 'testset_paths', nargs='*', help="testset file path") + parser.add_argument( + '--no-html-report', action='store_true', default=False, + help="do not generate html report.") parser.add_argument( '--html-report-name', help="specify html report name, only effective when generating html report.") @@ -78,13 +81,17 @@ def main_hrun(): create_scaffold(project_path) exit(0) - result = HttpRunner(args.testset_paths, failfast=args.failfast).run( - html_report_name=args.html_report_name, - html_report_template=args.html_report_template - ) + runner = HttpRunner(failfast=args.failfast).run(args.testset_paths) - print_output(result["output"]) - return 0 if result["success"] else 1 + if not args.no_html_report: + runner.gen_html_report( + html_report_name=args.html_report_name, + html_report_template=args.html_report_template + ) + + summary = runner.summary + print_output(summary["output"]) + return 0 if summary["success"] else 1 def main_locust(): """ Performance test with locust: parse command line options and run commands. diff --git a/httprunner/report.py b/httprunner/report.py index cb89f454..e853324e 100644 --- a/httprunner/report.py +++ b/httprunner/report.py @@ -53,9 +53,56 @@ def get_summary(result): 'duration': result.duration } summary["records"] = result.records + else: + summary["records"] = [] return summary +def render_html_report(summary, html_report_name=None, html_report_template=None): + """ render html report with specified report name and template + if html_report_name is not specified, use current datetime + if html_report_template is not specified, use default report template + """ + if not html_report_template: + html_report_template = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + "templates", + "default_report_template.html" + ) + logger.log_debug("No html report template specified, use default.") + else: + logger.log_info("render with html report template: {}".format(html_report_template)) + + logger.log_info("Start to render Html report ...") + logger.log_debug("render data: {}".format(summary)) + + report_dir_path = os.path.join(os.getcwd(), "reports") + start_datetime = summary["time"]["start_at"].strftime('%Y-%m-%d-%H-%M-%S') + if html_report_name: + summary["html_report_name"] = html_report_name + report_dir_path = os.path.join(report_dir_path, html_report_name) + html_report_name += "-{}.html".format(start_datetime) + else: + summary["html_report_name"] = "" + html_report_name = "{}.html".format(start_datetime) + + if not os.path.isdir(report_dir_path): + os.makedirs(report_dir_path) + + for record in summary.get("records"): + record["meta_data"] = make_json_serializable(record["meta_data"]) + + with io.open(html_report_template, "r", encoding='utf-8') as fp_r: + template_content = fp_r.read() + report_path = os.path.join(report_dir_path, html_report_name) + with io.open(report_path, 'w', encoding='utf-8') as fp_w: + rendered_content = Template(template_content).render(summary) + fp_w.write(rendered_content) + + logger.log_info("Generated Html report: {}".format(report_path)) + + return report_path + def make_json_serializable(raw_json): serializable_json = {} for key, value in raw_json.items(): @@ -86,12 +133,6 @@ class HtmlTestResult(unittest.TextTestResult): def __init__(self, stream, descriptions, verbosity): super(HtmlTestResult, self).__init__(stream, descriptions, verbosity) self.records = [] - self.default_report_template_path = os.path.join( - os.path.abspath(os.path.dirname(__file__)), - "templates", - "default_report_template.html" - ) - self.report_path = None def _record_test(self, test, status, attachment=''): self.records.append({ @@ -99,7 +140,7 @@ class HtmlTestResult(unittest.TextTestResult): 'status': status, 'response_time_ms': test.meta_data.get("response_time(ms)", 0), 'attachment': attachment, - "meta_data": make_json_serializable(test.meta_data) + "meta_data": test.meta_data }) def startTestRun(self): @@ -143,46 +184,3 @@ class HtmlTestResult(unittest.TextTestResult): @property def duration(self): return time.time() - self.start_at - - @property - def summary(self): - return get_summary(self) - - def render_html_report(self, html_report_name=None, html_report_template=None): - """ render html report with specified report name and template - if html_report_name is not specified, use current datetime - if html_report_template is not specified, use default report template - """ - if not html_report_template: - html_report_template = self.default_report_template_path - logger.log_debug("No html report template specified, use default.") - else: - logger.log_info("render with html report template: {}".format(html_report_template)) - - summary = self.summary - logger.log_info("Start to render Html report ...") - logger.log_debug("render data: {}".format(summary)) - - report_dir_path = os.path.join(os.getcwd(), "reports") - start_datetime = summary["time"]["start_at"].strftime('%Y-%m-%d-%H-%M-%S') - if html_report_name: - summary["html_report_name"] = html_report_name - report_dir_path = os.path.join(report_dir_path, html_report_name) - html_report_name += "-{}.html".format(start_datetime) - else: - summary["html_report_name"] = "" - html_report_name = "{}.html".format(start_datetime) - - if not os.path.isdir(report_dir_path): - os.makedirs(report_dir_path) - - with io.open(html_report_template, "r", encoding='utf-8') as fp_r: - template_content = fp_r.read() - report_path = os.path.join(report_dir_path, html_report_name) - with io.open(report_path, 'w', encoding='utf-8') as fp_w: - rendered_content = Template(template_content).render(summary) - fp_w.write(rendered_content) - - logger.log_info("Generated Html report: {}".format(report_path)) - - return report_path diff --git a/httprunner/task.py b/httprunner/task.py index 746db3ff..314a8bc6 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -6,7 +6,7 @@ import unittest from httprunner import exception, logger, runner, testcase, utils from httprunner.compat import is_py3 -from httprunner.report import HtmlTestResult, get_summary +from httprunner.report import HtmlTestResult, get_summary, render_html_report class TestCase(unittest.TestCase): @@ -189,58 +189,51 @@ class TaskSuite(unittest.TestSuite): class HttpRunner(object): - def __init__(self, path, gen_html_report=True, **kwargs): - """ initialize HttpRunner with specified testset file path and test runner - @param (str) path: - YAML/JSON testset file path - @param (boolean) gen_html_report: - True: use HtmlTestResult and generate html report - False: use TextTestResult and do not generate report file - @param (dict) kwargs: - key-value arguments used to initialize TextTestRunner - - failfast: False/True, stop the test run on the first error or failure. + def __init__(self, **kwargs): + """ initialize test runner + @param (dict) kwargs: key-value arguments used to initialize TextTestRunner + - resultclass: HtmlTestResult or TextTestResult + - failfast: False/True, stop the test run on the first error or failure. """ - self.path = path - - self.gen_html_report = gen_html_report - if self.gen_html_report: - kwargs["resultclass"] = HtmlTestResult - + kwargs.setdefault("resultclass", HtmlTestResult) self.runner = unittest.TextTestRunner(**kwargs) - def run(self, mapping=None, html_report_name=None, html_report_template=None): - """ start to run suite + def run(self, path, mapping=None): + """ start to run specified testset file with varaibles mapping + @param (str) path: + YAML/JSON testset file path @param (dict) mapping: if mapping specified, it will override variables in config block - @param (str) html_report_name: - output html report file name - @param (str) html_report_template: - report template file path, template should be in Jinja2 format """ try: mapping = mapping or {} - task_suite = TaskSuite(self.path, mapping) + task_suite = TaskSuite(path, mapping) except exception.TestcaseNotFound: - logger.log_error("Testcases not found in {}".format(self.path)) + logger.log_error("Testcases not found in {}".format(path)) sys.exit(1) result = self.runner.run(task_suite) + self.summary = get_summary(result) output = [] for task in task_suite.tasks: output.extend(task.output) - if self.gen_html_report: - summary = result.summary - summary["report_path"] = result.render_html_report( - html_report_name, - html_report_template - ) - else: - summary = get_summary(result) + self.summary["output"] = output + return self - summary["output"] = output - return summary + def gen_html_report(self, html_report_name=None, html_report_template=None): + """ generate html report and return report path + @param (str) html_report_name: + output html report file name + @param (str) html_report_template: + report template file path, template should be in Jinja2 format + """ + return render_html_report( + self.summary, + html_report_name, + html_report_template + ) class LocustTask(object): diff --git a/tests/test_httprunner.py b/tests/test_httprunner.py index 2558745a..77423df0 100644 --- a/tests/test_httprunner.py +++ b/tests/test_httprunner.py @@ -17,30 +17,21 @@ class TestHttpRunner(ApiServerUnittest): return self.api_client.get(url, headers=headers) def test_text_run_times(self): - kwargs = { - "gen_html_report": False - } - result = HttpRunner(self.testset_path, **kwargs).run() - self.assertEqual(result["stat"]["testsRun"], 10) + runner = HttpRunner().run(self.testset_path) + self.assertEqual(runner.summary["stat"]["testsRun"], 10) def test_text_skip(self): - kwargs = { - "gen_html_report": False - } - result = HttpRunner(self.testset_path, **kwargs).run() - self.assertEqual(result["stat"]["skipped"], 4) + runner = HttpRunner().run(self.testset_path) + self.assertEqual(runner.summary["stat"]["skipped"], 4) def test_html_report(self): - kwargs = { - "gen_html_report": True - } + kwargs = {} output_folder_name = os.path.basename(os.path.splitext(self.testset_path)[0]) - run_kwargs = { - "html_report_name": output_folder_name - } - result = HttpRunner(self.testset_path).run(**run_kwargs) - self.assertEqual(result["stat"]["testsRun"], 10) - self.assertEqual(result["stat"]["skipped"], 4) + runner = HttpRunner().run(self.testset_path) + summary = runner.summary + self.assertEqual(summary["stat"]["testsRun"], 10) + self.assertEqual(summary["stat"]["skipped"], 4) + runner.gen_html_report(html_report_name=output_folder_name) report_save_dir = os.path.join(os.getcwd(), 'reports', output_folder_name) shutil.rmtree(report_save_dir) diff --git a/tests/test_runner.py b/tests/test_runner.py index fa1cb33b..4627828c 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -77,52 +77,59 @@ class TestRunner(ApiServerUnittest): def test_run_testset_hardcode(self): for testcase_file_path in self.testcase_file_path_list: - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + self.assertTrue(runner.summary["success"]) def test_run_testsets_hardcode(self): - result = HttpRunner(self.testcase_file_path_list).run() - self.assertTrue(result["success"]) - self.assertEqual(result["stat"]["testsRun"], 6) - self.assertEqual(result["stat"]["successes"], 6) + runner = HttpRunner().run(self.testcase_file_path_list) + summary = runner.summary + self.assertTrue(summary["success"]) + self.assertEqual(summary["stat"]["testsRun"], 6) + self.assertEqual(summary["stat"]["successes"], 6) def test_run_testset_template_variables(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_variables.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) def test_run_testset_template_import_functions(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_template_import_functions.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) def test_run_testsets_template_import_functions(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_template_import_functions.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) def test_run_testsets_template_lambda_functions(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_template_lambda_functions.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) def test_run_testset_layered(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_layer.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) def test_run_testset_output(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_testset_layer.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) - self.assertIn("token", result["output"][0]["out"]) - self.assertEqual(len(result["output"]), 13) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) + self.assertIn("token", summary["output"][0]["out"]) + self.assertEqual(len(summary["output"]), 13) def test_run_testset_with_variables_mapping(self): testcase_file_path = os.path.join( @@ -130,10 +137,11 @@ class TestRunner(ApiServerUnittest): variables_mapping = { "app_version": '2.9.7' } - result = HttpRunner(testcase_file_path).run(mapping=variables_mapping) - self.assertTrue(result["success"]) - self.assertIn("token", result["output"][0]["out"]) - self.assertEqual(len(result["output"]), 13) + runner = HttpRunner().run(testcase_file_path, mapping=variables_mapping) + summary = runner.summary + self.assertTrue(summary["success"]) + self.assertIn("token", summary["output"][0]["out"]) + self.assertEqual(len(summary["output"]), 13) def test_run_testcase_with_empty_header(self): testcase_file_path = os.path.join( @@ -163,7 +171,8 @@ class TestRunner(ApiServerUnittest): def test_run_testset_with_parameters(self): testcase_file_path = os.path.join( os.getcwd(), 'tests/data/demo_parameters.yml') - result = HttpRunner(testcase_file_path).run() - self.assertTrue(result["success"]) - self.assertEqual(len(result["output"]), 3 * 2 * 2) - self.assertEqual(result["stat"]["testsRun"], 3 * 2 * 2) + runner = HttpRunner().run(testcase_file_path) + summary = runner.summary + self.assertTrue(summary["success"]) + self.assertEqual(len(summary["output"]), 3 * 2 * 2) + self.assertEqual(summary["stat"]["testsRun"], 3 * 2 * 2)