mirror of
https://github.com/httprunner/httprunner.git
synced 2026-07-06 23:11:21 +08:00
**Add**
- New feature: config part support "parameters". example: examples\postman_echo\request_methods\request_with_parameters.yml
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
config:
|
||||||
|
name: "request methods testcase: validate with functions"
|
||||||
|
parameters:
|
||||||
|
user_agent: ["iOS/10.1", "iOS/10.2"]
|
||||||
|
username-password: ${parameterize(request_methods/account.csv)}
|
||||||
|
app_version:
|
||||||
|
- ${get_httprunner_version()}
|
||||||
|
variables:
|
||||||
|
foo1: f1
|
||||||
|
base_url: "https://postman-echo.com"
|
||||||
|
verify: False
|
||||||
|
|
||||||
|
teststeps:
|
||||||
|
-
|
||||||
|
name: get with params
|
||||||
|
variables:
|
||||||
|
foo1: $username
|
||||||
|
foo2: $password
|
||||||
|
sum_v: "${sum_two(1, 2)}"
|
||||||
|
request:
|
||||||
|
method: GET
|
||||||
|
url: /get
|
||||||
|
params:
|
||||||
|
foo1: $foo1
|
||||||
|
foo2: $foo2
|
||||||
|
sum_v: $sum_v
|
||||||
|
headers:
|
||||||
|
User-Agent: $user_agent,$app_version
|
||||||
|
extract:
|
||||||
|
session_foo2: "body.args.foo2"
|
||||||
|
validate:
|
||||||
|
- eq: ["status_code", 200]
|
||||||
|
- eq: ["body.args.sum_v", "3"]
|
||||||
|
# - less_than: ["body.args.sum_v", "${sum_two(2, 2)}"] FIXME: TypeError: '<' not supported between instances of 'str' and 'int'
|
||||||
@@ -53,6 +53,7 @@ from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
class {{ class_name }}(HttpRunner):
|
class {{ class_name }}(HttpRunner):
|
||||||
|
{{ customization_test_start }}
|
||||||
config = {{ config_chain_style }}
|
config = {{ config_chain_style }}
|
||||||
|
|
||||||
teststeps = [
|
teststeps = [
|
||||||
@@ -407,6 +408,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
|
|||||||
"class_name": f"TestCase{testcase_cls_name}",
|
"class_name": f"TestCase{testcase_cls_name}",
|
||||||
"imports_list": imports_list,
|
"imports_list": imports_list,
|
||||||
"config_chain_style": make_config_chain_style(config),
|
"config_chain_style": make_config_chain_style(config),
|
||||||
|
"customization_test_start": make_test_start(config),
|
||||||
"teststeps_chain_style": [
|
"teststeps_chain_style": [
|
||||||
make_teststep_chain_style(step) for step in teststeps
|
make_teststep_chain_style(step) for step in teststeps
|
||||||
],
|
],
|
||||||
@@ -606,3 +608,22 @@ def init_make_parser(subparsers):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
|
def make_test_start(config: Dict) -> Text:
|
||||||
|
test_start_style = ""
|
||||||
|
if config["parameters"]:
|
||||||
|
params = config["parameters"]
|
||||||
|
test_start_style = f"""
|
||||||
|
import pytest
|
||||||
|
from httprunner.parser import parse_parameters
|
||||||
|
|
||||||
|
param = [{params}]
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('parametrize', parse_parameters(param))
|
||||||
|
def test_start(self, parametrize):
|
||||||
|
super().test_start(parametrize)
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
return test_start_style
|
||||||
@@ -463,3 +463,102 @@ def parse_variables_mapping(
|
|||||||
parsed_variables[var_name] = parsed_value
|
parsed_variables[var_name] = parsed_value
|
||||||
|
|
||||||
return parsed_variables
|
return parsed_variables
|
||||||
|
|
||||||
|
|
||||||
|
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None):
|
||||||
|
""" parse parameters and generate cartesian product.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parameters (list) parameters: parameter name and value in list
|
||||||
|
parameter value may be in three types:
|
||||||
|
(1) data list, e.g. ["iOS/10.1", "iOS/10.2", "iOS/10.3"]
|
||||||
|
(2) call built-in parameterize function, "${parameterize(account.csv)}"
|
||||||
|
(3) call custom function in debugtalk.py, "${gen_app_version()}"
|
||||||
|
|
||||||
|
variables_mapping (dict): variables mapping loaded from testcase config
|
||||||
|
functions_mapping (dict): functions mapping loaded from debugtalk.py
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: cartesian product list
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> parameters = [
|
||||||
|
{"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]},
|
||||||
|
{"username-password": "${parameterize(account.csv)}"},
|
||||||
|
{"app_version": "${gen_app_version()}"}
|
||||||
|
]
|
||||||
|
>>> parse_parameters(parameters)
|
||||||
|
|
||||||
|
"""
|
||||||
|
from httprunner.loader import load_project_meta
|
||||||
|
variables_mapping = variables_mapping or {}
|
||||||
|
functions_mapping = functions_mapping or {}
|
||||||
|
parsed_parameters_list = []
|
||||||
|
# project_meta = load_project_meta("")
|
||||||
|
# functions_mapping.update(project_meta.functions)
|
||||||
|
# logger.warning(f"functions_mapping: {functions_mapping}")
|
||||||
|
|
||||||
|
parameters = utils.ensure_mapping_format(parameters)
|
||||||
|
for parameter_name, parameter_content in parameters.items():
|
||||||
|
parameter_name_list = parameter_name.split("-")
|
||||||
|
|
||||||
|
if isinstance(parameter_content, list):
|
||||||
|
# (1) data list
|
||||||
|
# e.g. {"app_version": ["2.8.5", "2.8.6"]}
|
||||||
|
# => [{"app_version": "2.8.5", "app_version": "2.8.6"}]
|
||||||
|
# e.g. {"username-password": [["user1", "111111"], ["test2", "222222"]}
|
||||||
|
# => [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}]
|
||||||
|
parameter_content_list = []
|
||||||
|
for parameter_item in parameter_content:
|
||||||
|
if not isinstance(parameter_item, (list, tuple)):
|
||||||
|
# "2.8.5" => ["2.8.5"]
|
||||||
|
parameter_item = [parameter_item]
|
||||||
|
|
||||||
|
# ["app_version"], ["2.8.5"] => {"app_version": "2.8.5"}
|
||||||
|
# ["username", "password"], ["user1", "111111"] => {"username": "user1", "password": "111111"}
|
||||||
|
parameter_content_dict = dict(zip(parameter_name_list, parameter_item))
|
||||||
|
|
||||||
|
parameter_content_list.append(parameter_content_dict)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
# (2) & (3)
|
||||||
|
parsed_variables_mapping = parse_variables_mapping(
|
||||||
|
variables_mapping,
|
||||||
|
functions_mapping
|
||||||
|
)
|
||||||
|
parsed_parameter_content = parse_string(
|
||||||
|
parameter_content,
|
||||||
|
parsed_variables_mapping,
|
||||||
|
functions_mapping
|
||||||
|
)
|
||||||
|
if not isinstance(parsed_parameter_content, list):
|
||||||
|
raise exceptions.ParamsError(f"{parsed_parameter_content} parameters syntax error!")
|
||||||
|
|
||||||
|
parameter_content_list = []
|
||||||
|
for parameter_item in parsed_parameter_content:
|
||||||
|
if isinstance(parameter_item, dict):
|
||||||
|
# get subset by parameter name
|
||||||
|
# {"app_version": "${gen_app_version()}"}
|
||||||
|
# gen_app_version() => [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}]
|
||||||
|
# {"username-password": "${get_account()}"}
|
||||||
|
# get_account() => [
|
||||||
|
# {"username": "user1", "password": "111111"},
|
||||||
|
# {"username": "user2", "password": "222222"}
|
||||||
|
# ]
|
||||||
|
parameter_dict = {key: parameter_item[key] for key in parameter_name_list}
|
||||||
|
# elif isinstance(parameter_item, (list, tuple)):
|
||||||
|
# # {"username-password": "${get_account()}"}
|
||||||
|
# # get_account() => [("user1", "111111"), ("user2", "222222")]
|
||||||
|
# parameter_dict = dict(zip(parameter_name_list, parameter_item))
|
||||||
|
elif len(parameter_name_list) == 1:
|
||||||
|
# {"user_agent": "${get_user_agent()}"}
|
||||||
|
# get_user_agent() => ["iOS/10.1", "iOS/10.2"]
|
||||||
|
parameter_dict = {
|
||||||
|
parameter_name_list[0]: parameter_item
|
||||||
|
}
|
||||||
|
|
||||||
|
parameter_content_list.append(parameter_dict)
|
||||||
|
|
||||||
|
parsed_parameters_list.append(parameter_content_list)
|
||||||
|
|
||||||
|
return utils.gen_cartesian_product(*parsed_parameters_list)
|
||||||
|
|||||||
@@ -421,7 +421,7 @@ class HttpRunner(object):
|
|||||||
step_datas=self.__step_datas,
|
step_datas=self.__step_datas,
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_start(self) -> "HttpRunner":
|
def test_start(self, parametrize=None) -> "HttpRunner":
|
||||||
"""main entrance, discovered by pytest"""
|
"""main entrance, discovered by pytest"""
|
||||||
self.__init_tests__()
|
self.__init_tests__()
|
||||||
self.__project_meta = self.__project_meta or load_project_meta(
|
self.__project_meta = self.__project_meta or load_project_meta(
|
||||||
@@ -435,6 +435,7 @@ class HttpRunner(object):
|
|||||||
|
|
||||||
# parse config name
|
# parse config name
|
||||||
config_variables = self.__config.variables
|
config_variables = self.__config.variables
|
||||||
|
config_variables.update(parametrize)
|
||||||
config_variables.update(self.__session_variables)
|
config_variables.update(self.__session_variables)
|
||||||
self.__config.name = parse_data(
|
self.__config.name = parse_data(
|
||||||
self.__config.name, config_variables, self.__project_meta.functions
|
self.__config.name, config_variables, self.__project_meta.functions
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import os.path
|
|||||||
import platform
|
import platform
|
||||||
import uuid
|
import uuid
|
||||||
from multiprocessing import Queue
|
from multiprocessing import Queue
|
||||||
|
import itertools
|
||||||
from typing import Dict, List, Any
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
@@ -220,3 +221,79 @@ def is_support_multiprocessing() -> bool:
|
|||||||
except (ImportError, OSError):
|
except (ImportError, OSError):
|
||||||
# system that does not support semaphores(dependency of multiprocessing), like Android termux
|
# system that does not support semaphores(dependency of multiprocessing), like Android termux
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_mapping_format(variables):
|
||||||
|
""" ensure variables are in mapping format.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
variables (list/dict): original variables
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: ensured variables in dict format
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
>>> variables = [
|
||||||
|
{"a": 1},
|
||||||
|
{"b": 2}
|
||||||
|
]
|
||||||
|
>>> print(ensure_mapping_format(variables))
|
||||||
|
{
|
||||||
|
"a": 1,
|
||||||
|
"b": 2
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(variables, list):
|
||||||
|
variables_dict = {}
|
||||||
|
for map_dict in variables:
|
||||||
|
variables_dict.update(map_dict)
|
||||||
|
|
||||||
|
return variables_dict
|
||||||
|
|
||||||
|
elif isinstance(variables, dict):
|
||||||
|
return variables
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise exceptions.ParamsError("variables format error!")
|
||||||
|
|
||||||
|
|
||||||
|
def gen_cartesian_product(*args):
|
||||||
|
""" generate cartesian product for lists
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args (list of list): lists to be generated with cartesian product
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: cartesian product in list
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
|
||||||
|
>>> arg1 = [{"a": 1}, {"a": 2}]
|
||||||
|
>>> arg2 = [{"x": 111, "y": 112}, {"x": 121, "y": 122}]
|
||||||
|
>>> args = [arg1, arg2]
|
||||||
|
>>> gen_cartesian_product(*args)
|
||||||
|
>>> # same as below
|
||||||
|
>>> gen_cartesian_product(arg1, arg2)
|
||||||
|
[
|
||||||
|
{'a': 1, 'x': 111, 'y': 112},
|
||||||
|
{'a': 1, 'x': 121, 'y': 122},
|
||||||
|
{'a': 2, 'x': 111, 'y': 112},
|
||||||
|
{'a': 2, 'x': 121, 'y': 122}
|
||||||
|
]
|
||||||
|
|
||||||
|
"""
|
||||||
|
if not args:
|
||||||
|
return []
|
||||||
|
elif len(args) == 1:
|
||||||
|
return args[0]
|
||||||
|
|
||||||
|
product_list = []
|
||||||
|
for product_item_tuple in itertools.product(*args):
|
||||||
|
product_item_dict = {}
|
||||||
|
for item in product_item_tuple:
|
||||||
|
product_item_dict.update(item)
|
||||||
|
|
||||||
|
product_list.append(product_item_dict)
|
||||||
|
|
||||||
|
return product_list
|
||||||
|
|||||||
Reference in New Issue
Block a user