change: remove testsuite

This commit is contained in:
debugtalk
2022-04-23 15:04:35 +08:00
parent 24fcdfb418
commit 74299a5a5c
4 changed files with 71 additions and 191 deletions
+15 -30
View File
@@ -11,14 +11,13 @@ from loguru import logger
from pydantic import ValidationError from pydantic import ValidationError
from httprunner import builtin, exceptions, utils 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 project_meta: Union[ProjectMeta, None] = None
def _load_yaml_file(yaml_file: Text) -> Dict: 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: with open(yaml_file, mode="rb") as stream:
try: try:
yaml_content = yaml.load(stream, Loader=yaml.FullLoader) 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: 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: with open(json_file, mode="rb") as data_file:
try: try:
json_content = json.load(data_file) json_content = json.load(data_file)
@@ -81,20 +79,8 @@ def load_testcase_file(testcase_file: Text) -> TestCase:
return testcase_obj 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: def load_dot_env_file(dot_env_path: Text) -> Dict:
""" load .env file. """load .env file.
Args: Args:
dot_env_path (str): .env file path 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]: 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: Args:
csv_file (str): csv file path, csv file content is like below: 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: 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: Args:
folder_path (str): specified folder path to load 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]: def load_module_functions(module) -> Dict[Text, Callable]:
""" load python module functions. """load python module functions.
Args: Args:
module: python module module: python module
@@ -251,13 +237,12 @@ def load_module_functions(module) -> Dict[Text, Callable]:
def load_builtin_functions() -> Dict[Text, Callable]: def load_builtin_functions() -> Dict[Text, Callable]:
""" load builtin module functions """load builtin module functions"""
"""
return load_module_functions(builtin) return load_module_functions(builtin)
def locate_file(start_path: Text, file_name: Text) -> Text: 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. searching will be recursive upward until system root dir.
Args: Args:
@@ -295,7 +280,7 @@ def locate_file(start_path: Text, file_name: Text) -> Text:
def locate_debugtalk_py(start_path: Text) -> Text: def locate_debugtalk_py(start_path: Text) -> Text:
""" locate debugtalk.py file """locate debugtalk.py file
Args: Args:
start_path (str): start locating path, 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]: 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: Args:
test_path: specified testfile path 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]: 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. debugtalk.py should be located in project root directory.
Returns: Returns:
@@ -376,7 +361,7 @@ def load_debugtalk_functions() -> Dict[Text, Callable]:
def load_project_meta(test_path: Text, reload: bool = False) -> ProjectMeta: 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 testcases folder is relative to project_root_directory
by default, project_meta will be loaded only once, unless set reload to true. 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: 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: Args:
abs_path: absolute path 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}" f"project_meta.RootDir: {_project_meta.RootDir}"
) )
return abs_path[len(_project_meta.RootDir) + 1:] return abs_path[len(_project_meta.RootDir) + 1 :]
+34 -95
View File
@@ -8,14 +8,21 @@ import jinja2
from loguru import logger from loguru import logger
from httprunner import __version__, exceptions from httprunner import __version__, exceptions
from httprunner.compat import (convert_variables, ensure_path_sep, from httprunner.compat import (
ensure_testcase_v3, ensure_testcase_v3_api) convert_variables,
from httprunner.loader import (convert_relative_project_root_dir, ensure_path_sep,
load_folder_files, load_project_meta, ensure_testcase_v3,
load_test_file, load_testcase, load_testsuite) 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.response import uniform_validator
from httprunner.utils import (ga_client, is_support_multiprocessing, from httprunner.utils import ga_client, is_support_multiprocessing
merge_variables)
""" cache converted pytest files, avoid duplicate making """ cache converted pytest files, avoid duplicate making
""" """
@@ -71,10 +78,10 @@ if __name__ == "__main__":
def __ensure_absolute(path: Text) -> Text: def __ensure_absolute(path: Text) -> Text:
if path.startswith("./"): if path.startswith("./"):
# Linux/Darwin, hrun ./test.yml # Linux/Darwin, hrun ./test.yml
path = path[len("./"):] path = path[len("./") :]
elif path.startswith(".\\"): elif path.startswith(".\\"):
# Windows, hrun .\\test.yml # Windows, hrun .\\test.yml
path = path[len(".\\"):] path = path[len(".\\") :]
path = ensure_path_sep(path) path = ensure_path_sep(path)
project_meta = load_project_meta(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: 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: Args:
file_abs_path: absolute file path 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): 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") init_file = os.path.join(os.path.dirname(path), "__init__.py")
if os.path.isfile(init_file): if os.path.isfile(init_file):
return return
@@ -431,61 +437,8 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
return testcase_python_abs_path 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): 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 generated pytest file path will be cached in pytest_files_made_cache_mapping
Args: Args:
@@ -526,13 +479,13 @@ def __make(tests_path: Text):
if "config" not in test_content: if "config" not in test_content:
logger.warning( logger.warning(
f"Invalid testcase/testsuite file: {test_file}\n" f"Invalid testcase file: {test_file}\n"
f"reason: missing config part." f"reason: missing config part."
) )
continue continue
elif not isinstance(test_content["config"], Dict): elif not isinstance(test_content["config"], Dict):
logger.warning( 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']}" f"reason: config should be dict type, got {test_content['config']}"
) )
continue continue
@@ -540,33 +493,19 @@ def __make(tests_path: Text):
# ensure path absolute # ensure path absolute
test_content.setdefault("config", {})["path"] = test_file 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 # 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( logger.warning(
f"Invalid test file: {test_file}\n" f"Invalid testcase file: {test_file}\n{type(ex).__name__}: {ex}"
f"reason: file content is neither testcase nor testsuite"
) )
continue
def main_make(tests_paths: List[Text]) -> List[Text]: 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): 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( 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( parser.add_argument(
"testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path" "testcase_path", nargs="*", help="Specify YAML/JSON testcase file/folder path"
+13 -46
View File
@@ -73,7 +73,8 @@ from request_methods.request_with_functions_test import (
content, content,
) )
self.assertIn( self.assertIn(
".call(RequestWithFunctions)", content, ".call(RequestWithFunctions)",
content,
) )
def test_make_testcase_folder(self): 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): def test_ensure_file_path_valid(self):
self.assertEqual( self.assertEqual(
ensure_file_abs_path_valid( 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", "2 3.yml")
),
os.path.join(self.data_dir, "a_b_c", "T2_3.yml"), os.path.join(self.data_dir, "a_b_c", "T2_3.yml"),
) )
loader.project_meta = None loader.project_meta = None
@@ -113,67 +112,31 @@ from request_methods.request_with_functions_test import (
) )
loader.project_meta = None loader.project_meta = None
self.assertEqual( self.assertEqual(
ensure_file_abs_path_valid(os.getcwd()), os.getcwd(), ensure_file_abs_path_valid(os.getcwd()),
os.getcwd(),
) )
loader.project_meta = None loader.project_meta = None
self.assertEqual( self.assertEqual(
ensure_file_abs_path_valid( ensure_file_abs_path_valid(os.path.join(self.data_dir, ".csv")),
os.path.join(self.data_dir, ".csv")
),
os.path.join(self.data_dir, ".csv"), os.path.join(self.data_dir, ".csv"),
) )
def test_convert_testcase_path(self): def test_convert_testcase_path(self):
self.assertEqual( self.assertEqual(
convert_testcase_path( convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "2 3.yml")),
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"), os.path.join(self.data_dir, "a_b_c", "T2_3_test.py"),
"T23", "T23",
), ),
) )
self.assertEqual( self.assertEqual(
convert_testcase_path( convert_testcase_path(os.path.join(self.data_dir, "a-b.c", "中文case.yml")),
os.path.join(self.data_dir, "a-b.c", "中文case.yml")
),
( (
os.path.join(self.data_dir, "a_b_c", "中文case_test.py"), os.path.join(self.data_dir, "a_b_c", "中文case_test.py"),
"中文Case", "中文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): def test_make_config_chain_style(self):
config = { config = {
"name": "request methods testcase: validate with functions", "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): def test_make_teststep_chain_style(self):
step = { step = {
"name": "get with params", "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": { "request": {
"method": "GET", "method": "GET",
"url": "/get", "url": "/get",
+9 -20
View File
@@ -97,7 +97,9 @@ class ProjectMeta(BaseModel):
dot_env_path: Text = "" # .env file path dot_env_path: Text = "" # .env file path
functions: FunctionsMapping = {} # functions defined in debugtalk.py functions: FunctionsMapping = {} # functions defined in debugtalk.py
env: Env = {} 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): class TestsMapping(BaseModel):
@@ -166,21 +168,20 @@ class SessionData(BaseModel):
class StepResult(BaseModel): class StepResult(BaseModel):
"""teststep data, each step maybe corresponding to one request or one testcase""" """teststep data, each step maybe corresponding to one request or one testcase"""
name: Text = "" # teststep name name: Text = "" # teststep name
step_type: Text = "" # teststep type, request or testcase step_type: Text = "" # teststep type, request or testcase
success: bool = False success: bool = False
data: Union[SessionData, List['StepResult']] = None data: Union[SessionData, List["StepResult"]] = None
elapsed: float = 0.0 # teststep elapsed time elapsed: float = 0.0 # teststep elapsed time
content_size: float = 0 # response content size content_size: float = 0 # response content size
export_vars: VariablesMapping = {} export_vars: VariablesMapping = {}
attachment: Text = "" # teststep attachment attachment: Text = "" # teststep attachment
StepResult.update_forward_refs() StepResult.update_forward_refs()
class IStep(object): class IStep(object):
def name(self) -> str: def name(self) -> str:
raise NotImplementedError raise NotImplementedError
@@ -211,18 +212,6 @@ class PlatformInfo(BaseModel):
platform: Text 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): class Stat(BaseModel):
total: int = 0 total: int = 0
success: int = 0 success: int = 0