mirror of
https://github.com/httprunner/httprunner.git
synced 2026-05-12 02:21:29 +08:00
change: remove testsuite
This commit is contained in:
@@ -11,14 +11,13 @@ from loguru import logger
|
||||
from pydantic import ValidationError
|
||||
|
||||
from httprunner import builtin, exceptions, utils
|
||||
from httprunner.models import ProjectMeta, TestCase, TestSuite
|
||||
from httprunner.models import ProjectMeta, TestCase
|
||||
|
||||
project_meta: Union[ProjectMeta, None] = None
|
||||
|
||||
|
||||
def _load_yaml_file(yaml_file: Text) -> Dict:
|
||||
""" load yaml file and check file content format
|
||||
"""
|
||||
"""load yaml file and check file content format"""
|
||||
with open(yaml_file, mode="rb") as stream:
|
||||
try:
|
||||
yaml_content = yaml.load(stream, Loader=yaml.FullLoader)
|
||||
@@ -31,8 +30,7 @@ def _load_yaml_file(yaml_file: Text) -> Dict:
|
||||
|
||||
|
||||
def _load_json_file(json_file: Text) -> Dict:
|
||||
""" load json file and check file content format
|
||||
"""
|
||||
"""load json file and check file content format"""
|
||||
with open(json_file, mode="rb") as data_file:
|
||||
try:
|
||||
json_content = json.load(data_file)
|
||||
@@ -81,20 +79,8 @@ def load_testcase_file(testcase_file: Text) -> TestCase:
|
||||
return testcase_obj
|
||||
|
||||
|
||||
def load_testsuite(testsuite: Dict) -> TestSuite:
|
||||
path = testsuite["config"]["path"]
|
||||
try:
|
||||
# validate with pydantic TestCase model
|
||||
testsuite_obj = TestSuite.parse_obj(testsuite)
|
||||
except ValidationError as ex:
|
||||
err_msg = f"TestSuite ValidationError:\nfile: {path}\nerror: {ex}"
|
||||
raise exceptions.TestSuiteFormatError(err_msg)
|
||||
|
||||
return testsuite_obj
|
||||
|
||||
|
||||
def load_dot_env_file(dot_env_path: Text) -> Dict:
|
||||
""" load .env file.
|
||||
"""load .env file.
|
||||
|
||||
Args:
|
||||
dot_env_path (str): .env file path
|
||||
@@ -140,7 +126,7 @@ def load_dot_env_file(dot_env_path: Text) -> Dict:
|
||||
|
||||
|
||||
def load_csv_file(csv_file: Text) -> List[Dict]:
|
||||
""" load csv file and check file content format
|
||||
"""load csv file and check file content format
|
||||
|
||||
Args:
|
||||
csv_file (str): csv file path, csv file content is like below:
|
||||
@@ -186,7 +172,7 @@ def load_csv_file(csv_file: Text) -> List[Dict]:
|
||||
|
||||
|
||||
def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
|
||||
""" load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
|
||||
"""load folder path, return all files endswith .yml/.yaml/.json/_test.py in list.
|
||||
|
||||
Args:
|
||||
folder_path (str): specified folder path to load
|
||||
@@ -227,7 +213,7 @@ def load_folder_files(folder_path: Text, recursive: bool = True) -> List:
|
||||
|
||||
|
||||
def load_module_functions(module) -> Dict[Text, Callable]:
|
||||
""" load python module functions.
|
||||
"""load python module functions.
|
||||
|
||||
Args:
|
||||
module: python module
|
||||
@@ -251,13 +237,12 @@ def load_module_functions(module) -> Dict[Text, Callable]:
|
||||
|
||||
|
||||
def load_builtin_functions() -> Dict[Text, Callable]:
|
||||
""" load builtin module functions
|
||||
"""
|
||||
"""load builtin module functions"""
|
||||
return load_module_functions(builtin)
|
||||
|
||||
|
||||
def locate_file(start_path: Text, file_name: Text) -> Text:
|
||||
""" locate filename and return absolute file path.
|
||||
"""locate filename and return absolute file path.
|
||||
searching will be recursive upward until system root dir.
|
||||
|
||||
Args:
|
||||
@@ -295,7 +280,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
|
||||
|
||||
|
||||
def locate_debugtalk_py(start_path: Text) -> Text:
|
||||
""" locate debugtalk.py file
|
||||
"""locate debugtalk.py file
|
||||
|
||||
Args:
|
||||
start_path (str): start locating path,
|
||||
@@ -315,7 +300,7 @@ def locate_debugtalk_py(start_path: Text) -> Text:
|
||||
|
||||
|
||||
def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
|
||||
""" locate debugtalk.py path as project root directory
|
||||
"""locate debugtalk.py path as project root directory
|
||||
|
||||
Args:
|
||||
test_path: specified testfile path
|
||||
@@ -352,7 +337,7 @@ def locate_project_root_directory(test_path: Text) -> Tuple[Text, Text]:
|
||||
|
||||
|
||||
def load_debugtalk_functions() -> Dict[Text, Callable]:
|
||||
""" load project debugtalk.py module functions
|
||||
"""load project debugtalk.py module functions
|
||||
debugtalk.py should be located in project root directory.
|
||||
|
||||
Returns:
|
||||
@@ -376,7 +361,7 @@ def load_debugtalk_functions() -> Dict[Text, Callable]:
|
||||
|
||||
|
||||
def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
|
||||
""" load testcases, .env, debugtalk.py functions.
|
||||
"""load testcases, .env, debugtalk.py functions.
|
||||
testcases folder is relative to project_root_directory
|
||||
by default, project_meta will be loaded only once, unless set reload to true.
|
||||
|
||||
@@ -428,7 +413,7 @@ def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta:
|
||||
|
||||
|
||||
def convert_relative_project_root_dir(abs_path: Text) -> Text:
|
||||
""" convert absolute path to relative path, based on project_meta.RootDir
|
||||
"""convert absolute path to relative path, based on project_meta.RootDir
|
||||
|
||||
Args:
|
||||
abs_path: absolute path
|
||||
@@ -444,4 +429,4 @@ def convert_relative_project_root_dir(abs_path: Text) -> Text:
|
||||
f"project_meta.RootDir: {_project_meta.RootDir}"
|
||||
)
|
||||
|
||||
return abs_path[len(_project_meta.RootDir) + 1:]
|
||||
return abs_path[len(_project_meta.RootDir) + 1 :]
|
||||
|
||||
@@ -8,14 +8,21 @@ import jinja2
|
||||
from loguru import logger
|
||||
|
||||
from httprunner import __version__, exceptions
|
||||
from httprunner.compat import (convert_variables, ensure_path_sep,
|
||||
ensure_testcase_v3, ensure_testcase_v3_api)
|
||||
from httprunner.loader import (convert_relative_project_root_dir,
|
||||
load_folder_files, load_project_meta,
|
||||
load_test_file, load_testcase, load_testsuite)
|
||||
from httprunner.compat import (
|
||||
convert_variables,
|
||||
ensure_path_sep,
|
||||
ensure_testcase_v3,
|
||||
ensure_testcase_v3_api,
|
||||
)
|
||||
from httprunner.loader import (
|
||||
convert_relative_project_root_dir,
|
||||
load_folder_files,
|
||||
load_project_meta,
|
||||
load_test_file,
|
||||
load_testcase,
|
||||
)
|
||||
from httprunner.response import uniform_validator
|
||||
from httprunner.utils import (ga_client, is_support_multiprocessing,
|
||||
merge_variables)
|
||||
from httprunner.utils import ga_client, is_support_multiprocessing
|
||||
|
||||
""" cache converted pytest files, avoid duplicate making
|
||||
"""
|
||||
@@ -71,10 +78,10 @@ if __name__ == "__main__":
|
||||
def __ensure_absolute(path: Text) -> Text:
|
||||
if path.startswith("./"):
|
||||
# Linux/Darwin, hrun ./test.yml
|
||||
path = path[len("./"):]
|
||||
path = path[len("./") :]
|
||||
elif path.startswith(".\\"):
|
||||
# Windows, hrun .\\test.yml
|
||||
path = path[len(".\\"):]
|
||||
path = path[len(".\\") :]
|
||||
|
||||
path = ensure_path_sep(path)
|
||||
project_meta = load_project_meta(path)
|
||||
@@ -92,7 +99,7 @@ def __ensure_absolute(path: Text) -> Text:
|
||||
|
||||
|
||||
def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
|
||||
""" ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
|
||||
"""ensure file path valid for pytest, handle cases when directory name includes dot/hyphen/space
|
||||
|
||||
Args:
|
||||
file_abs_path: absolute file path
|
||||
@@ -133,8 +140,7 @@ def ensure_file_abs_path_valid(file_abs_path: Text) -> Text:
|
||||
|
||||
|
||||
def __ensure_testcase_module(path: Text):
|
||||
""" ensure pytest files are in python module, generate __init__.py on demand
|
||||
"""
|
||||
"""ensure pytest files are in python module, generate __init__.py on demand"""
|
||||
init_file = os.path.join(os.path.dirname(path), "__init__.py")
|
||||
if os.path.isfile(init_file):
|
||||
return
|
||||
@@ -431,61 +437,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
|
||||
return testcase_python_abs_path
|
||||
|
||||
|
||||
def make_testsuite(testsuite: Dict):
|
||||
"""convert valid testsuite dict to pytest folder with testcases"""
|
||||
# validate testsuite format
|
||||
load_testsuite(testsuite)
|
||||
|
||||
testsuite_config = testsuite["config"]
|
||||
testsuite_path = testsuite_config["path"]
|
||||
testsuite_variables = convert_variables(
|
||||
testsuite_config.get("variables", {}), testsuite_path
|
||||
)
|
||||
|
||||
logger.info(f"start to make testsuite: {testsuite_path}")
|
||||
|
||||
# create directory with testsuite file name, put its testcases under this directory
|
||||
testsuite_path = ensure_file_abs_path_valid(testsuite_path)
|
||||
testsuite_dir, file_suffix = os.path.splitext(testsuite_path)
|
||||
# demo_testsuite.yml => demo_testsuite_yml
|
||||
testsuite_dir = f"{testsuite_dir}_{file_suffix.lstrip('.')}"
|
||||
|
||||
for testcase in testsuite["testcases"]:
|
||||
# get referenced testcase content
|
||||
testcase_file = testcase["testcase"]
|
||||
testcase_path = __ensure_absolute(testcase_file)
|
||||
testcase_dict = load_test_file(testcase_path)
|
||||
testcase_dict.setdefault("config", {})
|
||||
testcase_dict["config"]["path"] = 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 verify
|
||||
if "verify" in testsuite_config:
|
||||
testcase_dict["config"]["verify"] = testsuite_config["verify"]
|
||||
# override variables
|
||||
# testsuite testcase variables > testsuite config variables
|
||||
testcase_variables = convert_variables(
|
||||
testcase.get("variables", {}), testcase_path
|
||||
)
|
||||
testcase_variables = merge_variables(testcase_variables, testsuite_variables)
|
||||
# testsuite testcase variables > testcase config variables
|
||||
testcase_dict["config"]["variables"] = convert_variables(
|
||||
testcase_dict["config"].get("variables", {}), testcase_path
|
||||
)
|
||||
testcase_dict["config"]["variables"].update(testcase_variables)
|
||||
|
||||
# make testcase
|
||||
testcase_pytest_path = make_testcase(testcase_dict, testsuite_dir)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
|
||||
|
||||
def __make(tests_path: Text):
|
||||
""" make testcase(s) with testcase/testsuite/folder absolute path
|
||||
"""make testcase(s) with testcase/folder absolute path
|
||||
generated pytest file path will be cached in pytest_files_made_cache_mapping
|
||||
|
||||
Args:
|
||||
@@ -526,13 +479,13 @@ def __make(tests_path: Text):
|
||||
|
||||
if "config" not in test_content:
|
||||
logger.warning(
|
||||
f"Invalid testcase/testsuite file: {test_file}\n"
|
||||
f"Invalid testcase file: {test_file}\n"
|
||||
f"reason: missing config part."
|
||||
)
|
||||
continue
|
||||
elif not isinstance(test_content["config"], Dict):
|
||||
logger.warning(
|
||||
f"Invalid testcase/testsuite file: {test_file}\n"
|
||||
f"Invalid testcase file: {test_file}\n"
|
||||
f"reason: config should be dict type, got {test_content['config']}"
|
||||
)
|
||||
continue
|
||||
@@ -540,33 +493,19 @@ def __make(tests_path: Text):
|
||||
# ensure path absolute
|
||||
test_content.setdefault("config", {})["path"] = test_file
|
||||
|
||||
# testcase
|
||||
if "teststeps" in test_content:
|
||||
try:
|
||||
testcase_pytest_path = make_testcase(test_content)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
except exceptions.TestCaseFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
# testsuite
|
||||
elif "testcases" in test_content:
|
||||
try:
|
||||
make_testsuite(test_content)
|
||||
except exceptions.TestSuiteFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid testsuite file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
# invalid format
|
||||
else:
|
||||
if "teststeps" not in test_content:
|
||||
logger.warning(f"Invalid testcase file: {test_file}")
|
||||
|
||||
# testcase
|
||||
try:
|
||||
testcase_pytest_path = make_testcase(test_content)
|
||||
pytest_files_run_set.add(testcase_pytest_path)
|
||||
except exceptions.TestCaseFormatError as ex:
|
||||
logger.warning(
|
||||
f"Invalid test file: {test_file}\n"
|
||||
f"reason: file content is neither testcase nor testsuite"
|
||||
f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
def main_make(tests_paths: List[Text]) -> List[Text]:
|
||||
@@ -594,10 +533,10 @@ def main_make(tests_paths: List[Text]) -> List[Text]:
|
||||
|
||||
|
||||
def init_make_parser(subparsers):
|
||||
""" make testcases: parse command line options and run commands.
|
||||
"""
|
||||
"""make testcases: parse command line options and run commands."""
|
||||
parser = subparsers.add_parser(
|
||||
"make", help="Convert YAML/JSON testcases to pytest cases.",
|
||||
"make",
|
||||
help="Convert YAML/JSON testcases to pytest cases.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path"
|
||||
|
||||
@@ -73,7 +73,8 @@ from request_methods.request_with_functions_test import (
|
||||
content,
|
||||
)
|
||||
self.assertIn(
|
||||
".call(RequestWithFunctions)", content,
|
||||
".call(RequestWithFunctions)",
|
||||
content,
|
||||
)
|
||||
|
||||
def test_make_testcase_folder(self):
|
||||
@@ -94,9 +95,7 @@ from request_methods.request_with_functions_test import (
|
||||
|
||||
def test_ensure_file_path_valid(self):
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(
|
||||
os.path.join(self.data_dir, "a-b.c", "2 3.yml")
|
||||
),
|
||||
ensure_file_abs_path_valid(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
|
||||
os.path.join(self.data_dir, "a_b_c", "T2_3.yml"),
|
||||
)
|
||||
loader.project_meta = None
|
||||
@@ -113,67 +112,31 @@ from request_methods.request_with_functions_test import (
|
||||
)
|
||||
loader.project_meta = None
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(os.getcwd()), os.getcwd(),
|
||||
ensure_file_abs_path_valid(os.getcwd()),
|
||||
os.getcwd(),
|
||||
)
|
||||
loader.project_meta = None
|
||||
self.assertEqual(
|
||||
ensure_file_abs_path_valid(
|
||||
os.path.join(self.data_dir, ".csv")
|
||||
),
|
||||
ensure_file_abs_path_valid(os.path.join(self.data_dir, ".csv")),
|
||||
os.path.join(self.data_dir, ".csv"),
|
||||
)
|
||||
|
||||
def test_convert_testcase_path(self):
|
||||
self.assertEqual(
|
||||
convert_testcase_path(
|
||||
os.path.join(self.data_dir, "a-b.c", "2 3.yml")
|
||||
),
|
||||
convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
|
||||
(
|
||||
os.path.join(self.data_dir, "a_b_c", "T2_3_test.py"),
|
||||
"T23",
|
||||
),
|
||||
)
|
||||
self.assertEqual(
|
||||
convert_testcase_path(
|
||||
os.path.join(self.data_dir, "a-b.c", "中文case.yml")
|
||||
),
|
||||
convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "中文case.yml")),
|
||||
(
|
||||
os.path.join(self.data_dir, "a_b_c", "中文case_test.py"),
|
||||
"中文Case",
|
||||
),
|
||||
)
|
||||
|
||||
def test_make_testsuite(self):
|
||||
path = ["examples/postman_echo/request_methods/demo_testsuite.yml"]
|
||||
testcase_python_list = main_make(path)
|
||||
self.assertEqual(len(testcase_python_list), 2)
|
||||
self.assertIn(
|
||||
os.path.join(
|
||||
os.getcwd(),
|
||||
os.path.join(
|
||||
"examples",
|
||||
"postman_echo",
|
||||
"request_methods",
|
||||
"demo_testsuite_yml",
|
||||
"request_with_functions_test.py",
|
||||
),
|
||||
),
|
||||
testcase_python_list,
|
||||
)
|
||||
self.assertIn(
|
||||
os.path.join(
|
||||
os.getcwd(),
|
||||
os.path.join(
|
||||
"examples",
|
||||
"postman_echo",
|
||||
"request_methods",
|
||||
"demo_testsuite_yml",
|
||||
"request_with_testcase_reference_test.py",
|
||||
),
|
||||
),
|
||||
testcase_python_list,
|
||||
)
|
||||
|
||||
def test_make_config_chain_style(self):
|
||||
config = {
|
||||
"name": "request methods testcase: validate with functions",
|
||||
@@ -190,7 +153,11 @@ from request_methods.request_with_functions_test import (
|
||||
def test_make_teststep_chain_style(self):
|
||||
step = {
|
||||
"name": "get with params",
|
||||
"variables": {"foo1": "bar1", "foo2": 123, "sum_v": "${sum_two(1, 2)}",},
|
||||
"variables": {
|
||||
"foo1": "bar1",
|
||||
"foo2": 123,
|
||||
"sum_v": "${sum_two(1, 2)}",
|
||||
},
|
||||
"request": {
|
||||
"method": "GET",
|
||||
"url": "/get",
|
||||
|
||||
@@ -97,7 +97,9 @@ class ProjectMeta(BaseModel):
|
||||
dot_env_path: Text = "" # .env file path
|
||||
functions: FunctionsMapping = {} # functions defined in debugtalk.py
|
||||
env: Env = {}
|
||||
RootDir: Text = os.getcwd() # project root directory (ensure absolute), the path debugtalk.py located
|
||||
RootDir: Text = (
|
||||
os.getcwd()
|
||||
) # project root directory (ensure absolute), the path debugtalk.py located
|
||||
|
||||
|
||||
class TestsMapping(BaseModel):
|
||||
@@ -166,21 +168,20 @@ class SessionData(BaseModel):
|
||||
class StepResult(BaseModel):
|
||||
"""teststep data, each step maybe corresponding to one request or one testcase"""
|
||||
|
||||
name: Text = "" # teststep name
|
||||
step_type: Text = "" # teststep type, request or testcase
|
||||
name: Text = "" # teststep name
|
||||
step_type: Text = "" # teststep type, request or testcase
|
||||
success: bool = False
|
||||
data: Union[SessionData, List['StepResult']] = None
|
||||
elapsed: float = 0.0 # teststep elapsed time
|
||||
content_size: float = 0 # response content size
|
||||
data: Union[SessionData, List["StepResult"]] = None
|
||||
elapsed: float = 0.0 # teststep elapsed time
|
||||
content_size: float = 0 # response content size
|
||||
export_vars: VariablesMapping = {}
|
||||
attachment: Text = "" # teststep attachment
|
||||
attachment: Text = "" # teststep attachment
|
||||
|
||||
|
||||
StepResult.update_forward_refs()
|
||||
|
||||
|
||||
class IStep(object):
|
||||
|
||||
def name(self) -> str:
|
||||
raise NotImplementedError
|
||||
|
||||
@@ -211,18 +212,6 @@ class PlatformInfo(BaseModel):
|
||||
platform: Text
|
||||
|
||||
|
||||
class TestCaseRef(BaseModel):
|
||||
name: Text
|
||||
base_url: Text = ""
|
||||
testcase: Text
|
||||
variables: VariablesMapping = {}
|
||||
|
||||
|
||||
class TestSuite(BaseModel):
|
||||
config: TConfig
|
||||
testcases: List[TestCaseRef]
|
||||
|
||||
|
||||
class Stat(BaseModel):
|
||||
total: int = 0
|
||||
success: int = 0
|
||||
|
||||
Reference in New Issue
Block a user