mirror of
https://github.com/httprunner/httprunner.git
synced 2026-06-06 16:29:37 +08:00
feat: convert YAML/JSON testcase to python testcase
This commit is contained in:
@@ -2,89 +2,81 @@ from httprunner.runner import TestCaseRunner
|
|||||||
from httprunner.schema import TestsConfig, TestStep
|
from httprunner.schema import TestsConfig, TestStep
|
||||||
|
|
||||||
|
|
||||||
class TestCaseRequestMethodsWithVariables(TestCaseRunner):
|
class TestCaseRequestWithVariables(TestCaseRunner):
|
||||||
config = TestsConfig(**{
|
config = TestsConfig(
|
||||||
"name": "request methods testcase with variables",
|
**{
|
||||||
"variables": {
|
"name": "request methods testcase with variables",
|
||||||
"foo1": "session_bar1"
|
"variables": {"foo1": "session_bar1"},
|
||||||
},
|
"base_url": "https://postman-echo.com",
|
||||||
"base_url": "https://postman-echo.com",
|
"verify": False,
|
||||||
"verify": False
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
teststeps = [
|
teststeps = [
|
||||||
TestStep(**{
|
TestStep(
|
||||||
"name": "get with params",
|
**{
|
||||||
"variables": {
|
"name": "get with params",
|
||||||
"foo1": "bar1",
|
"variables": {"foo1": "bar1", "foo2": "session_bar2"},
|
||||||
"foo2": "session_bar2"
|
"request": {
|
||||||
},
|
"method": "GET",
|
||||||
"request": {
|
"url": "/get",
|
||||||
"method": "GET",
|
"params": {"foo1": "$foo1", "foo2": "$foo2"},
|
||||||
"url": "/get",
|
"headers": {"User-Agent": "HttpRunner/3.0"},
|
||||||
"params": {
|
|
||||||
"foo1": "$foo1",
|
|
||||||
"foo2": "$foo2"
|
|
||||||
},
|
},
|
||||||
"headers": {
|
"extract": {"session_foo2": "body.args.foo2"},
|
||||||
"User-Agent": "HttpRunner/3.0"
|
"validate": [
|
||||||
}
|
{"eq": ["status_code", 200]},
|
||||||
},
|
{"eq": ["body.args.foo1", "session_bar1"]},
|
||||||
"extract": {
|
{"eq": ["body.args.foo2", "session_bar2"]},
|
||||||
"session_foo2": "body.args.foo2"
|
],
|
||||||
},
|
}
|
||||||
"validate": [
|
),
|
||||||
{"eq": ["status_code", 200]},
|
TestStep(
|
||||||
{"eq": ["body.args.foo1", "session_bar1"]},
|
**{
|
||||||
{"eq": ["body.args.foo2", "session_bar2"]}
|
"name": "post raw text",
|
||||||
]
|
"variables": {"foo1": "hello world", "foo3": "$session_foo2"},
|
||||||
}),
|
"request": {
|
||||||
TestStep(**{
|
"method": "POST",
|
||||||
"name": "post raw text",
|
"url": "/post",
|
||||||
"variables": {
|
"headers": {
|
||||||
"foo1": "hello world",
|
"User-Agent": "HttpRunner/3.0",
|
||||||
"foo3": "$session_foo2"
|
"Content-Type": "text/plain",
|
||||||
},
|
},
|
||||||
"request": {
|
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
|
||||||
"method": "POST",
|
},
|
||||||
"url": "/post",
|
"validate": [
|
||||||
"data": "This is expected to be sent back as part of response body: $foo1-$foo3.",
|
{"eq": ["status_code", 200]},
|
||||||
"headers": {
|
{
|
||||||
"User-Agent": "HttpRunner/3.0",
|
"eq": [
|
||||||
"Content-Type": "text/plain"
|
"body.data",
|
||||||
}
|
"This is expected to be sent back as part of response body: session_bar1-session_bar2.",
|
||||||
},
|
]
|
||||||
"validate": [
|
},
|
||||||
{"eq": ["status_code", 200]},
|
],
|
||||||
{"eq": [
|
}
|
||||||
"body.data",
|
),
|
||||||
"This is expected to be sent back as part of response body: session_bar1-session_bar2."
|
TestStep(
|
||||||
]},
|
**{
|
||||||
]
|
"name": "post form data",
|
||||||
}),
|
"variables": {"foo1": "bar1", "foo2": "bar2"},
|
||||||
TestStep(**{
|
"request": {
|
||||||
"name": "post form data",
|
"method": "POST",
|
||||||
"variables": {
|
"url": "/post",
|
||||||
"foo1": "session_bar1",
|
"headers": {
|
||||||
"foo2": "bar2"
|
"User-Agent": "HttpRunner/3.0",
|
||||||
},
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
"request": {
|
},
|
||||||
"method": "POST",
|
"data": "foo1=$foo1&foo2=$foo2",
|
||||||
"url": "/post",
|
},
|
||||||
"data": "foo1=$foo1&foo2=$foo2",
|
"validate": [
|
||||||
"headers": {
|
{"eq": ["status_code", 200]},
|
||||||
"User-Agent": "HttpRunner/3.0",
|
{"eq": ["body.form.foo1", "session_bar1"]},
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
{"eq": ["body.form.foo2", "bar2"]},
|
||||||
}
|
],
|
||||||
},
|
}
|
||||||
"validate": [
|
),
|
||||||
{"eq": ["status_code", 200]},
|
|
||||||
{"eq": ["body.form.foo1", "session_bar1"]},
|
|
||||||
{"eq": ["body.form.foo2", "bar2"]}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
TestCaseRequestMethodsWithVariables().run()
|
TestCaseRequestWithVariables().run()
|
||||||
|
|||||||
11
httprunner/loader_test.py
Normal file
11
httprunner/loader_test.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import unittest
|
||||||
|
from httprunner.new_loader import load_testcase_file
|
||||||
|
|
||||||
|
|
||||||
|
class TestLoader(unittest.TestCase):
|
||||||
|
|
||||||
|
def test_load_testcase_file(self):
|
||||||
|
path = "examples/postman_echo/request_methods/request_with_variables.yml"
|
||||||
|
testcase = load_testcase_file(path)
|
||||||
|
self.assertEqual(testcase.config.name, "request methods testcase with variables")
|
||||||
|
self.assertEqual(len(testcase.teststeps), 3)
|
||||||
49
httprunner/make.py
Normal file
49
httprunner/make.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
|
import jinja2
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner.new_loader import load_testcase_file
|
||||||
|
|
||||||
|
__TMPL__ = """
|
||||||
|
from httprunner.runner import TestCaseRunner
|
||||||
|
from httprunner.schema import TestsConfig, TestStep
|
||||||
|
|
||||||
|
|
||||||
|
class {{ class_name }}(TestCaseRunner):
|
||||||
|
config = TestsConfig(**{{ config }})
|
||||||
|
|
||||||
|
teststeps = [
|
||||||
|
{% for teststep in teststeps %}
|
||||||
|
TestStep(**{{ teststep }}),
|
||||||
|
{% endfor %}
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
{{ class_name }}().run()
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def make_testcase(path: str) -> str:
|
||||||
|
testcase = load_testcase_file(path)
|
||||||
|
template = jinja2.Template(__TMPL__)
|
||||||
|
|
||||||
|
raw_file_name, _ = os.path.splitext(os.path.basename(path))
|
||||||
|
# convert title case, e.g. request_with_variables => RequestWithVariables
|
||||||
|
name_in_title_case = raw_file_name.title().replace("_", "")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"class_name": f"TestCase{name_in_title_case}",
|
||||||
|
"config": testcase["config"],
|
||||||
|
"teststeps": testcase["teststeps"],
|
||||||
|
}
|
||||||
|
content = template.render(data)
|
||||||
|
|
||||||
|
testcase_dir = os.path.dirname(path)
|
||||||
|
testcase_python_path = os.path.join(testcase_dir, f"{raw_file_name}_test.py")
|
||||||
|
with open(testcase_python_path, "w") as f:
|
||||||
|
f.write(content)
|
||||||
|
|
||||||
|
logger.info(f"generated testcase: {testcase_python_path}")
|
||||||
|
return testcase_python_path
|
||||||
135
httprunner/new_loader.py
Normal file
135
httprunner/new_loader.py
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import io
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import types
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
from loguru import logger
|
||||||
|
|
||||||
|
from httprunner import builtin
|
||||||
|
from httprunner import exceptions
|
||||||
|
from httprunner.schema import TestCase
|
||||||
|
|
||||||
|
try:
|
||||||
|
# PyYAML version >= 5.1
|
||||||
|
# ref: https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load(input)-Deprecation
|
||||||
|
yaml.warnings({"YAMLLoadWarning": False})
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def _load_yaml_file(yaml_file):
|
||||||
|
""" load yaml file and check file content format
|
||||||
|
"""
|
||||||
|
with io.open(yaml_file, "r", encoding="utf-8") as stream:
|
||||||
|
try:
|
||||||
|
yaml_content = yaml.load(stream)
|
||||||
|
except yaml.YAMLError as ex:
|
||||||
|
logger.error(str(ex))
|
||||||
|
raise exceptions.FileFormatError
|
||||||
|
|
||||||
|
return yaml_content
|
||||||
|
|
||||||
|
|
||||||
|
def _load_json_file(json_file):
|
||||||
|
""" load json file and check file content format
|
||||||
|
"""
|
||||||
|
with io.open(json_file, encoding="utf-8") as data_file:
|
||||||
|
try:
|
||||||
|
json_content = json.load(data_file)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
err_msg = f"JSONDecodeError: JSON file format error: {json_file}"
|
||||||
|
logger.error(err_msg)
|
||||||
|
raise exceptions.FileFormatError(err_msg)
|
||||||
|
|
||||||
|
return json_content
|
||||||
|
|
||||||
|
|
||||||
|
def load_testcase_file(testcase_file):
|
||||||
|
"""load testcase file and validate with pydantic model"""
|
||||||
|
file_suffix = os.path.splitext(testcase_file)[1].lower()
|
||||||
|
if file_suffix == ".json":
|
||||||
|
testcase_content = _load_json_file(testcase_file)
|
||||||
|
elif file_suffix in [".yaml", ".yml"]:
|
||||||
|
testcase_content = _load_yaml_file(testcase_file)
|
||||||
|
else:
|
||||||
|
# '' or other suffix
|
||||||
|
raise exceptions.FileFormatError(
|
||||||
|
f"testcase file should be YAML/JSON format, invalid testcase file: {testcase_file}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# validate with pydantic TestCase model
|
||||||
|
TestCase.parse_obj(testcase_content)
|
||||||
|
|
||||||
|
return testcase_content
|
||||||
|
|
||||||
|
|
||||||
|
def load_folder_files(folder_path, recursive=True):
|
||||||
|
""" load folder path, return all files endswith yml/yaml/json in list.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
folder_path (str): specified folder path to load
|
||||||
|
recursive (bool): load files recursively if True
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: files endswith yml/yaml/json
|
||||||
|
"""
|
||||||
|
if isinstance(folder_path, (list, set)):
|
||||||
|
files = []
|
||||||
|
for path in set(folder_path):
|
||||||
|
files.extend(load_folder_files(path, recursive))
|
||||||
|
|
||||||
|
return files
|
||||||
|
|
||||||
|
if not os.path.exists(folder_path):
|
||||||
|
return []
|
||||||
|
|
||||||
|
file_list = []
|
||||||
|
|
||||||
|
for dirpath, dirnames, filenames in os.walk(folder_path):
|
||||||
|
filenames_list = []
|
||||||
|
|
||||||
|
for filename in filenames:
|
||||||
|
if not filename.endswith((".yml", ".yaml", ".json")):
|
||||||
|
continue
|
||||||
|
|
||||||
|
filenames_list.append(filename)
|
||||||
|
|
||||||
|
for filename in filenames_list:
|
||||||
|
file_path = os.path.join(dirpath, filename)
|
||||||
|
file_list.append(file_path)
|
||||||
|
|
||||||
|
if not recursive:
|
||||||
|
break
|
||||||
|
|
||||||
|
return file_list
|
||||||
|
|
||||||
|
|
||||||
|
def load_module_functions(module):
|
||||||
|
""" load python module functions.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module: python module
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: functions mapping for specified python module
|
||||||
|
|
||||||
|
{
|
||||||
|
"func1_name": func1,
|
||||||
|
"func2_name": func2
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
module_functions = {}
|
||||||
|
|
||||||
|
for name, item in vars(module).items():
|
||||||
|
if isinstance(item, types.FunctionType):
|
||||||
|
module_functions[name] = item
|
||||||
|
|
||||||
|
return module_functions
|
||||||
|
|
||||||
|
|
||||||
|
def load_builtin_functions():
|
||||||
|
""" load builtin module functions
|
||||||
|
"""
|
||||||
|
return load_module_functions(builtin)
|
||||||
Reference in New Issue
Block a user