refactor: parameters

This commit is contained in:
debugtalk
2020-07-06 15:23:35 +08:00
parent b39a806159
commit 0fc42f77cd
17 changed files with 130 additions and 178 deletions

View File

@@ -4,6 +4,7 @@ from httprunner import __version__
def get_httprunner_version():
return __version__
def sum_two(m, n):
return m + n
@@ -18,3 +19,7 @@ def get_testsuite_config_variables():
def get_app_version():
return [3.1, 3.0]
def calculate_two_nums(a, b=1):
return [a + b, b - a]

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_functions.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner):
config = (
Config("request with functions")
.variables(

View File

@@ -1,4 +1,4 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_testcase_reference.yml
import sys
@@ -14,6 +14,7 @@ from request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner):
config = (
Config("request with referenced testcase")
.variables(

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/hardcode.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseHardcode(HttpRunner):
config = (
Config("request methods testcase in hardcode")
.base_url("https://postman-echo.com")

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_functions.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithFunctions(HttpRunner):
config = (
Config("request methods testcase with functions")
.variables(

View File

@@ -1,23 +1,24 @@
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_parameters.yml
import pytest
from httprunner import parse_parameters
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithParameters(HttpRunner):
import pytest
from httprunner.parser import parse_parameters
param = [
{
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"app_version": "${get_app_version()}",
}
]
@pytest.mark.parametrize("parameters", parse_parameters(param))
@pytest.mark.parametrize(
"parameters",
parse_parameters(
{
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"app_version": "${get_app_version()}",
}
),
)
def test_start(self, parameters):
super().test_start(parameters)

View File

@@ -1,4 +1,4 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_testcase_reference.yml
import sys
@@ -14,6 +14,7 @@ from request_methods.request_with_functions_test import (
class TestCaseRequestWithTestcaseReference(HttpRunner):
config = (
Config("request methods testcase: reference testcase")
.variables(

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/request_with_variables.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseRequestWithVariables(HttpRunner):
config = (
Config("request methods testcase with variables")
.variables(**{"foo1": "testcase_config_bar1", "foo2": "testcase_config_bar2"})

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/validate_with_functions.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithFunctions(HttpRunner):
config = (
Config("request methods testcase: validate with functions")
.variables(**{"foo1": "session_bar1"})

View File

@@ -1,10 +1,11 @@
# NOTE: Generated By HttpRunner v3.1.2
# NOTE: Generated By HttpRunner v3.1.3
# FROM: request_methods/validate_with_variables.yml
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
class TestCaseValidateWithVariables(HttpRunner):
config = (
Config("request methods testcase: validate with variables")
.variables(**{"foo1": "session_bar1"})

View File

@@ -3,6 +3,7 @@ __description__ = "One-stop solution for HTTP(S) testing."
# import firstly for monkey patch if needed
from httprunner.ext.locust import main_locusts
from httprunner.parser import parse_parameters
from httprunner.runner import HttpRunner
from httprunner.testcase import Config, Step, RunRequest, RunTestCase
@@ -14,4 +15,5 @@ __all__ = [
"Step",
"RunRequest",
"RunTestCase",
"parse_parameters",
]

View File

@@ -37,23 +37,31 @@ pytest_files_run_set: Set = set()
__TEMPLATE__ = jinja2.Template(
"""# NOTE: Generated By HttpRunner v{{ version }}
# FROM: {{ testcase_path }}
{% if imports_list and diff_levels > 0 %}
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__)
{% for _ in range(diff_levels) %}
.parent
{% endfor %}
))
sys.path.insert(0, str(Path(__file__){% for _ in range(diff_levels) %}.parent{% endfor %}))
{% endif %}
{% if parameters %}
import pytest
from httprunner import parse_parameters
{% endif %}
from httprunner import HttpRunner, Config, Step, RunRequest, RunTestCase
{% for import_str in imports_list %}
{{ import_str }}
{% endfor %}
class {{ class_name }}(HttpRunner):
{{ customization_test_start }}
{% if parameters %}
@pytest.mark.parametrize('parameters', parse_parameters({{parameters}}))
def test_start(self, parameters):
super().test_start(parameters)
{% endif %}
config = {{ config_chain_style }}
teststeps = [
@@ -411,7 +419,7 @@ def make_testcase(testcase: Dict, dir_path: Text = None) -> Text:
"class_name": f"TestCase{testcase_cls_name}",
"imports_list": imports_list,
"config_chain_style": make_config_chain_style(config),
"customization_test_start": make_test_start_style(config),
"parameters": config.get("parameters"),
"teststeps_chain_style": [
make_teststep_chain_style(step) for step in teststeps
],
@@ -611,22 +619,3 @@ def init_make_parser(subparsers):
)
return parser
def make_test_start_style(config: Dict) -> Text:
test_start_style = ""
if "parameters" in config.keys():
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

View File

@@ -1,7 +1,7 @@
import ast
import builtins
import re
from typing import Any, Set, Text, Callable, List, Dict
from typing import Any, Set, Text, Callable, List, Dict, Union
from loguru import logger
from sentry_sdk import capture_exception
@@ -465,52 +465,44 @@ def parse_variables_mapping(
return parsed_variables
def parse_parameters(parameters, variables_mapping=None, functions_mapping=None):
def parse_parameters(parameters: Dict,) -> List[Dict]:
""" parse parameters and generate cartesian product.
Args:
parameters (list) parameters: parameter name and value in list
parameters (Dict) parameters: parameter name and value mapping
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()}"}
]
>>> 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)
"""
variables_mapping = variables_mapping or {}
functions_mapping = functions_mapping or {}
parsed_parameters_list = []
parsed_parameters_list: List[List[Dict]] = []
# load project_meta functions
from httprunner.loader import load_project_meta
project_meta = loader.load_project_meta("")
functions_mapping = project_meta.functions
project_meta = load_project_meta("")
functions_mapping.update(project_meta.functions)
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):
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 = []
parameter_content_list: List[Dict] = []
for parameter_item in parameter_content:
if not isinstance(parameter_item, (list, tuple)):
# "2.8.5" => ["2.8.5"]
@@ -519,24 +511,21 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
# ["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:
elif isinstance(parameter_content, Text):
# (2) & (3)
parsed_variables_mapping = parse_variables_mapping(
variables_mapping, functions_mapping
parsed_parameter_content: List = parse_data(
parameter_content, {}, functions_mapping
)
parsed_parameter_content = parse_data(
parameter_content, parsed_variables_mapping, functions_mapping
)
if not isinstance(parsed_parameter_content, list):
if not isinstance(parsed_parameter_content, List):
raise exceptions.ParamsError(
f"{parsed_parameter_content} parameters syntax error!"
f"parameters content should be in List type, got {parsed_parameter_content} for {parameter_content}"
)
parameter_content_list = []
parameter_content_list: List[Dict] = []
for parameter_item in parsed_parameter_content:
if isinstance(parameter_item, dict):
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'}]
@@ -545,20 +534,39 @@ def parse_parameters(parameters, variables_mapping=None, functions_mapping=None)
# {"username": "user1", "password": "111111"},
# {"username": "user2", "password": "222222"}
# ]
parameter_dict = {
parameter_dict: 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 isinstance(parameter_item, (List, tuple)):
if len(parameter_name_list) == len(parameter_item):
# {"username-password": "${get_account()}"}
# get_account() => [("user1", "111111"), ("user2", "222222")]
parameter_dict = dict(zip(parameter_name_list, parameter_item))
else:
raise exceptions.ParamsError(
f"parameter names length are not equal to value length.\n"
f"parameter names: {parameter_name_list}\n"
f"parameter values: {parameter_item}"
)
elif len(parameter_name_list) == 1:
# {"user_agent": "${get_user_agent()}"}
# get_user_agent() => ["iOS/10.1", "iOS/10.2"]
# parameter_dict will get: {"user_agent": "iOS/10.1", "user_agent": "iOS/10.2"}
parameter_dict = {parameter_name_list[0]: parameter_item}
else:
raise exceptions.ParamsError(
f"Invalid parameter names and values:\n"
f"parameter names: {parameter_name_list}\n"
f"parameter values: {parameter_item}"
)
parameter_content_list.append(parameter_dict)
else:
raise exceptions.ParamsError(
f"parameter content should be List or Text(variables or functions call), got {parameter_content}"
)
parsed_parameters_list.append(parameter_content_list)
return utils.gen_cartesian_product(*parsed_parameters_list)

View File

@@ -6,7 +6,7 @@ import platform
import uuid
from multiprocessing import Queue
import itertools
from typing import Dict, List, Any
from typing import Dict, List, Any, Union, Text
import sentry_sdk
from loguru import logger
@@ -223,42 +223,7 @@ def is_support_multiprocessing() -> bool:
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):
def gen_cartesian_product(*args: List[Dict]) -> List[Dict]:
""" generate cartesian product for lists
Args:

View File

@@ -1,6 +1,7 @@
import os
import unittest
from httprunner import loader
from httprunner.make import (
main_make,
convert_testcase_path,
@@ -9,9 +10,7 @@ from httprunner.make import (
make_teststep_chain_style,
pytest_files_run_set,
ensure_file_abs_path_valid,
make_test_start_style,
)
from httprunner import loader
class TestMake(unittest.TestCase):
@@ -215,24 +214,3 @@ from request_methods.request_with_functions_test import (
teststep_chain_style,
"""Step(RunRequest("get with params").with_variables(**{'foo1': 'bar1', 'foo2': 123, 'sum_v': '${sum_two(1, 2)}'}).get("/get").with_params(**{'foo1': '$foo1', 'foo2': '$foo2', 'sum_v': '$sum_v'}).with_headers(**{'User-Agent': 'HttpRunner/${get_httprunner_version()}'}).extract().with_jmespath('body.args.foo1', 'session_foo1').with_jmespath('body.args.foo2', 'session_foo2').validate().assert_equal("status_code", 200).assert_equal("body.args.sum_v", "3"))""",
)
def test_make_test_start_style(self):
params = {
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"app_version": "${get_app_version()}",
}
config = {"parameters": params}
self.assertEqual(
make_test_start_style(config),
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)
""",
)

View File

@@ -1,9 +1,10 @@
import os
import time
import unittest
from httprunner import parser
from httprunner.exceptions import VariableNotFound, FunctionNotFound
from httprunner.parser import regex_findall_variables
from httprunner.loader import load_project_meta
class TestParserBasic(unittest.TestCase):
@@ -27,17 +28,19 @@ class TestParserBasic(unittest.TestCase):
self.assertEqual(parser.parse_string_value("${func}"), "${func}")
def test_regex_findall_variables(self):
self.assertEqual(regex_findall_variables("$variable"), ["variable"])
self.assertEqual(regex_findall_variables("${variable}123"), ["variable"])
self.assertEqual(regex_findall_variables("/blog/$postid"), ["postid"])
self.assertEqual(regex_findall_variables("/$var1/$var2"), ["var1", "var2"])
self.assertEqual(regex_findall_variables("abc"), [])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$a"), ["a"])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$$a"), [])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$$$a"), ["a"])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$$$$a"), [])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$$a$b"), ["b"])
self.assertEqual(regex_findall_variables("Z:2>1*0*1+1$$a$$b"), [])
self.assertEqual(parser.regex_findall_variables("$variable"), ["variable"])
self.assertEqual(parser.regex_findall_variables("${variable}123"), ["variable"])
self.assertEqual(parser.regex_findall_variables("/blog/$postid"), ["postid"])
self.assertEqual(
parser.regex_findall_variables("/$var1/$var2"), ["var1", "var2"]
)
self.assertEqual(parser.regex_findall_variables("abc"), [])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$a"), ["a"])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$$a"), [])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$$$a"), ["a"])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$$$$a"), [])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$$a$b"), ["b"])
self.assertEqual(parser.regex_findall_variables("Z:2>1*0*1+1$$a$$b"), [])
def test_extract_variables(self):
self.assertEqual(parser.extract_variables("$var"), {"var"})
@@ -454,23 +457,22 @@ class TestParserBasic(unittest.TestCase):
self.assertEqual(parsed_testcase["headers"]["sum"], 3)
def test_parse_parameters_testcase(self):
variables = {
"user_agent": "chrome",
"sum": 5,
parameters = {
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"sum": "${calculate_two_nums(1, 2)}",
}
param = [
{
"user_agent": ["iOS/10.1", "iOS/10.2"],
"username-password": "${parameterize(request_methods/account.csv)}",
"sum": "${add_two_nums(1, 2)}",
}
]
load_project_meta(
os.path.join(
os.path.dirname(os.path.dirname(__file__)),
"examples",
"postman_echo",
"request_methods",
),
)
parsed_params = parser.parse_parameters(parameters)
self.assertEqual(len(parsed_params), 2 * 3 * 2)
functions = {
"add_two_nums": lambda a, b=1: [a + b, b - a],
}
parsed_params = parser.parse_parameters(param, variables, functions)
self.assertIn(
{
"username": "test1",

View File

@@ -126,12 +126,6 @@ class TestUtils(unittest.TestCase):
{"base_url": "https://httpbin.org", "foo1": "bar1"},
)
def test_ensure_mapping_format(self):
map_list = [{"a": 1}, {"b": 2}]
ordered_dict = utils.ensure_mapping_format(map_list)
self.assertIsInstance(ordered_dict, dict)
self.assertIn("a", ordered_dict)
def test_cartesian_product_one(self):
parameters_content_list = [[{"a": 1}, {"a": 2}]]
product_list = utils.gen_cartesian_product(*parameters_content_list)