mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-13 17:29:56 +08:00
239 lines
7.4 KiB
Python
239 lines
7.4 KiB
Python
import os
|
|
import subprocess
|
|
from typing import Union, Text, List, Tuple, Dict
|
|
|
|
import jinja2
|
|
from loguru import logger
|
|
|
|
from httprunner import exceptions
|
|
from httprunner.loader import (
|
|
load_folder_files,
|
|
load_test_file,
|
|
load_testcase,
|
|
load_testsuite,
|
|
load_project_meta,
|
|
)
|
|
from httprunner.parser import parse_data
|
|
|
|
__TMPL__ = """# NOTICE: Generated By HttpRunner. DO'NOT EDIT!
|
|
# FROM: {{ testcase_path }}
|
|
from httprunner import HttpRunner, TConfig, TStep
|
|
|
|
|
|
class {{ class_name }}(HttpRunner):
|
|
config = TConfig(**{{ config }})
|
|
|
|
teststeps = [
|
|
{% for teststep in teststeps %}
|
|
TStep(**{{ teststep }}),
|
|
{% endfor %}
|
|
]
|
|
|
|
if __name__ == "__main__":
|
|
{{ class_name }}().test_start()
|
|
|
|
"""
|
|
|
|
|
|
def convert_testcase_path(testcase_path: Text) -> Tuple[Text, Text]:
|
|
"""convert single YAML/JSON testcase path to python file"""
|
|
if os.path.isdir(testcase_path):
|
|
# folder does not need to convert
|
|
return testcase_path, ""
|
|
|
|
raw_file_name, file_suffix = os.path.splitext(os.path.basename(testcase_path))
|
|
|
|
file_suffix = file_suffix.lower()
|
|
if file_suffix not in [".json", ".yml", ".yaml"]:
|
|
raise exceptions.ParamsError(
|
|
"testcase file should have .yaml/.yml/.json suffix"
|
|
)
|
|
|
|
file_name = raw_file_name.replace(" ", "_").replace(".", "_").replace("-", "_")
|
|
testcase_dir = os.path.dirname(testcase_path)
|
|
testcase_python_path = os.path.join(testcase_dir, f"{file_name}_test.py")
|
|
|
|
# convert title case, e.g. request_with_variables => RequestWithVariables
|
|
name_in_title_case = file_name.title().replace("_", "")
|
|
|
|
return testcase_python_path, name_in_title_case
|
|
|
|
|
|
def format_pytest_with_black(python_paths: List[Text]):
|
|
logger.info("format pytest cases with black ...")
|
|
try:
|
|
subprocess.run(["black", *python_paths])
|
|
except subprocess.CalledProcessError as ex:
|
|
logger.error(ex)
|
|
|
|
|
|
def make_testcase(testcase: Dict) -> Union[str, None]:
|
|
"""convert valid testcase dict to pytest file path"""
|
|
try:
|
|
# validate testcase format
|
|
load_testcase(testcase)
|
|
except exceptions.TestCaseFormatError as ex:
|
|
logger.error(f"TestCaseFormatError: {ex}")
|
|
raise
|
|
|
|
testcase_path = testcase["config"]["path"]
|
|
logger.info(f"start to make testcase: {testcase_path}")
|
|
|
|
template = jinja2.Template(__TMPL__)
|
|
|
|
testcase_python_path, name_in_title_case = convert_testcase_path(testcase_path)
|
|
|
|
config = testcase["config"]
|
|
|
|
config.setdefault("variables", {})
|
|
if isinstance(config["variables"], Text):
|
|
# get variables by function, e.g. ${get_variables()}
|
|
project_meta = load_project_meta(testcase_path)
|
|
config["variables"] = parse_data(
|
|
config["variables"], {}, project_meta.functions
|
|
)
|
|
|
|
config["path"] = testcase_python_path
|
|
data = {
|
|
"testcase_path": testcase_path,
|
|
"class_name": f"TestCase{name_in_title_case}",
|
|
"config": config,
|
|
"teststeps": testcase["teststeps"],
|
|
}
|
|
content = template.render(data)
|
|
|
|
with open(testcase_python_path, "w") as f:
|
|
f.write(content)
|
|
|
|
logger.info(f"generated testcase: {testcase_python_path}")
|
|
return testcase_python_path
|
|
|
|
|
|
def make_testsuite(testsuite: Dict) -> List[Text]:
|
|
"""convert valid testsuite dict to pytest folder with testcases"""
|
|
try:
|
|
# validate testcase format
|
|
load_testsuite(testsuite)
|
|
except exceptions.TestSuiteFormatError as ex:
|
|
logger.error(f"TestSuiteFormatError: {ex}")
|
|
raise
|
|
|
|
config = testsuite["config"]
|
|
testsuite_path = config["path"]
|
|
project_meta = load_project_meta(testsuite_path)
|
|
project_working_directory = project_meta.PWD
|
|
|
|
testsuite_variables = config.get("variables", {})
|
|
if isinstance(testsuite_variables, Text):
|
|
# get variables by function, e.g. ${get_variables()}
|
|
testsuite_variables = parse_data(
|
|
testsuite_variables, {}, project_meta.functions
|
|
)
|
|
|
|
logger.info(f"start to make testsuite: {testsuite_path}")
|
|
|
|
# create directory with testsuite file name, put its testcases under this directory
|
|
testsuite_dir = testsuite_path.replace(".", "_")
|
|
os.makedirs(testsuite_dir, exist_ok=True)
|
|
|
|
testcase_files = []
|
|
|
|
for testcase in testsuite["testcases"]:
|
|
# get referenced testcase content
|
|
testcase_file = testcase["testcase"]
|
|
testcase_path = os.path.join(project_working_directory, testcase_file)
|
|
testcase_dict = load_test_file(testcase_path)
|
|
testcase_dict.setdefault("config", {})
|
|
testcase_dict["config"]["path"] = os.path.join(
|
|
testsuite_dir, os.path.basename(testcase_path)
|
|
)
|
|
|
|
# override testcase name
|
|
testcase_dict["config"]["name"] = testcase["name"]
|
|
# override base_url
|
|
base_url = testsuite["config"].get("base_url") or testcase.get("base_url")
|
|
if base_url:
|
|
testcase_dict["config"]["base_url"] = base_url
|
|
# override variables
|
|
testcase_dict["config"].setdefault("variables", {})
|
|
testcase_dict["config"]["variables"].update(testcase.get("variables", {}))
|
|
testcase_dict["config"]["variables"].update(testsuite_variables)
|
|
|
|
# make testcase
|
|
testcase_path = make_testcase(testcase_dict)
|
|
testcase_files.append(testcase_path)
|
|
|
|
return testcase_files
|
|
|
|
|
|
def __make(tests_path: Text) -> List:
|
|
test_files = []
|
|
if os.path.isdir(tests_path):
|
|
files_list = load_folder_files(tests_path)
|
|
test_files.extend(files_list)
|
|
elif os.path.isfile(tests_path):
|
|
test_files.append(tests_path)
|
|
else:
|
|
raise exceptions.TestcaseNotFound(f"Invalid tests path: {tests_path}")
|
|
|
|
testcase_path_list = []
|
|
for test_file in test_files:
|
|
try:
|
|
test_content = load_test_file(test_file)
|
|
test_content.setdefault("config", {})["path"] = test_file
|
|
except (exceptions.FileNotFound, exceptions.FileFormatError) as ex:
|
|
logger.warning(ex)
|
|
continue
|
|
|
|
# testcase
|
|
if "teststeps" in test_content:
|
|
try:
|
|
testcase_file = make_testcase(test_content)
|
|
except exceptions.TestCaseFormatError:
|
|
continue
|
|
|
|
testcase_path_list.append(testcase_file)
|
|
|
|
# testsuite
|
|
elif "testcases" in test_content:
|
|
try:
|
|
testcase_files = make_testsuite(test_content)
|
|
except exceptions.TestSuiteFormatError:
|
|
continue
|
|
|
|
testcase_path_list.extend(testcase_files)
|
|
|
|
# invalid format
|
|
else:
|
|
raise exceptions.FileFormatError(
|
|
f"test file is neither testcase nor testsuite: {test_file}"
|
|
)
|
|
|
|
if not testcase_path_list:
|
|
logger.warning(f"No valid testcase generated on {tests_path}")
|
|
return []
|
|
|
|
return testcase_path_list
|
|
|
|
|
|
def main_make(tests_paths: List[Text]) -> List:
|
|
testcase_path_list = []
|
|
for tests_path in tests_paths:
|
|
testcase_path_list.extend(__make(tests_path))
|
|
|
|
format_pytest_with_black(testcase_path_list)
|
|
return testcase_path_list
|
|
|
|
|
|
def init_make_parser(subparsers):
|
|
""" make testcases: parse command line options and run commands.
|
|
"""
|
|
parser = subparsers.add_parser(
|
|
"make", help="Convert YAML/JSON testcases to pytest cases.",
|
|
)
|
|
parser.add_argument(
|
|
"testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path"
|
|
)
|
|
|
|
return parser
|