refactor: use HttpRunner as testcase base class

This commit is contained in:
debugtalk
2020-05-15 20:05:17 +08:00
parent 80a600bccc
commit 2cd9eba6a3
12 changed files with 71 additions and 363 deletions

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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()

View File

@@ -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",
]

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()
"""

View File

@@ -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))

View File