mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
refactor: use HttpRunner as testcase base class
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
"""
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
0
httprunner/runner_test.py
Normal file
0
httprunner/runner_test.py
Normal file
Reference in New Issue
Block a user