diff --git a/.gitignore b/.gitignore index 31a72c46..b7d995e7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,13 +2,16 @@ __pycache__ .DS_Store .vscode +.idea .pypirc */tmp/* build/* dist/* *.egg-info .python-version -logs/% +logs .coverage locustfile.py site/ +poetry.lock +reports \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ccea9762..9d2b70c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Release History +## 2.2.6 (2019-09-18) + +**Added** + +- config variables support parsing from function + +**Changed** + +- remove unused import +- adjust code format + ## 2.2.5 (2019-07-28) **Added** diff --git a/httprunner/__init__.py b/httprunner/__init__.py index 567610eb..7fbb6af4 100644 --- a/httprunner/__init__.py +++ b/httprunner/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.2.5" +__version__ = "2.2.6" __description__ = "One-stop solution for HTTP(S) testing." __all__ = ["__version__", "__description__"] diff --git a/httprunner/cli.py b/httprunner/cli.py index f6000427..97b4b3ad 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -1,5 +1,6 @@ # encoding: utf-8 + def main_hrun(): """ API test: parse command line options and run commands. """ @@ -11,7 +12,7 @@ def main_hrun(): from httprunner.compat import is_py2 from httprunner.validator import validate_json_file from httprunner.utils import (create_scaffold, get_python2_retire_msg, - prettify_json_file) + prettify_json_file) parser = argparse.ArgumentParser(description=__description__) parser.add_argument( diff --git a/httprunner/loader.py b/httprunner/loader.py index 1df7a8a3..bf93b81e 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -17,10 +17,12 @@ try: except AttributeError: pass + ############################################################################### -## file loader +# file loader ############################################################################### + def _check_format(file_path, content): """ check testcase format if valid """ @@ -239,9 +241,10 @@ def locate_file(start_path, file_name): ############################################################################### -## debugtalk.py module loader +# debugtalk.py module loader ############################################################################### + def load_module_functions(module): """ load python module functions. @@ -290,9 +293,10 @@ def load_debugtalk_functions(): ############################################################################### -## testcase loader +# testcase loader ############################################################################### + project_mapping = {} tests_def_mapping = { "PWD": None, @@ -720,14 +724,16 @@ def load_api_folder(api_folder_path): if isinstance(api_items, list): for api_item in api_items: key, api_dict = api_item.popitem() - api_id = api_dict.get("id") or api_dict.get("def") or api_dict.get("name") + api_id = api_dict.get("id") or api_dict.get("def") \ + or api_dict.get("name") if key != "api" or not api_id: raise exceptions.ParamsError( "Invalid API defined in {}".format(api_file_path)) if api_id in api_definition_mapping: raise exceptions.ParamsError( - "Duplicated API ({}) defined in {}".format(api_id, api_file_path)) + "Duplicated API ({}) defined in {}".format( + api_id, api_file_path)) else: api_definition_mapping[api_id] = api_dict @@ -745,7 +751,8 @@ def locate_debugtalk_py(start_path): """ locate debugtalk.py file Args: - start_path (str): start locating path, maybe testcase file path or directory path + start_path (str): start locating path, + maybe testcase file path or directory path Returns: str: debugtalk.py file path, None if not found @@ -769,7 +776,8 @@ def load_project_tests(test_path, dot_env_path=None): dot_env_path (str): specified .env file path Returns: - dict: project loaded api/testcases definitions, environments and debugtalk.py functions. + dict: project loaded api/testcases definitions, + environments and debugtalk.py functions. """ # locate debugtalk.py file @@ -876,6 +884,7 @@ def load_tests(path, dot_env_path=None): } def __load_file_content(path): + loaded_content = None try: loaded_content = load_test_file(path) except exceptions.FileFormatError: diff --git a/httprunner/parser.py b/httprunner/parser.py index 4a85ac65..29b0a666 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -2,11 +2,10 @@ import ast import builtins -import os import re from httprunner import exceptions, utils, validator -from httprunner.compat import basestring, builtin_str, numeric_types, str +from httprunner.compat import basestring, numeric_types, str # use $$ to escape $ notation dolloar_regex_compile = re.compile(r"\$\$") @@ -872,10 +871,23 @@ def __prepare_config(config, project_mapping, session_variables_set=None): """ # get config variables raw_config_variables = config.pop("variables", {}) - raw_config_variables_mapping = utils.ensure_mapping_format(raw_config_variables) + override_variables = utils.deepcopy_dict(project_mapping.get("variables", {})) functions = project_mapping.get("functions", {}) + if isinstance(raw_config_variables, basestring) and function_regex_compile.match(raw_config_variables): + # config variables are generated by calling function + # e.g. + # "config": { + # "name": "basic test with httpbin", + # "variables": "${gen_variables()}" + # } + raw_config_variables_mapping = parse_lazy_data( + prepare_lazy_data(raw_config_variables, functions_mapping=functions) + ) + else: + raw_config_variables_mapping = utils.ensure_mapping_format(raw_config_variables) + # override config variables with passed in variables raw_config_variables_mapping.update(override_variables) diff --git a/httprunner/response.py b/httprunner/response.py index 5cf20189..5ab5ac33 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -6,8 +6,6 @@ import jsonpath from httprunner import exceptions, logger, utils from httprunner.compat import OrderedDict, basestring, is_py2 -from requests.models import PreparedRequest -from requests.structures import CaseInsensitiveDict text_extractor_regexp_compile = re.compile(r".*\(.*\).*") @@ -112,7 +110,7 @@ class ResponseObject(object): "content.person.name.first_name" """ - # string.split(sep=None, maxsplit=-1) -> list of strings + # string.split(sep=None, maxsplit=1) -> list of strings # e.g. "content.person.name" => ["content", "person.name"] try: top_query, sub_query = field.split('.', 1) diff --git a/pyproject.toml b/pyproject.toml index 02d00a78..db5bae3a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "httprunner" -version = "2.2.5" +version = "2.2.6" description = "One-stop solution for HTTP(S) testing." license = "Apache-2.0" readme = "README.md" diff --git a/tests/debugtalk.py b/tests/debugtalk.py index 74b9a3d3..dd89e8be 100644 --- a/tests/debugtalk.py +++ b/tests/debugtalk.py @@ -111,3 +111,10 @@ def alter_response(response): def alter_response_error(response): # NameError not_defined_variable + + +def gen_variables(): + return { + "var_a": 1, + "var_b": 2 + } diff --git a/tests/test_parser.py b/tests/test_parser.py index 5391ebc2..6f76148e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,5 +1,4 @@ import os -import re import time import unittest @@ -561,7 +560,6 @@ class TestParserBasic(unittest.TestCase): ) def test_parse_data_functions(self): - import random, string functions_mapping = { "gen_random_string": gen_random_string } diff --git a/tests/test_runner.py b/tests/test_runner.py index 4b684c1d..df8c734d 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -336,4 +336,47 @@ class TestRunner(ApiServerUnittest): parsed_testcases = parser.parse_tests(tests_mapping) parsed_testcase = parsed_testcases[0] test_runner = runner.Runner(parsed_testcase["config"]) - test_runner.run_test(parsed_testcase["teststeps"][0]) \ No newline at end of file + test_runner.run_test(parsed_testcase["teststeps"][0]) + + def test_run_testcase_config_variables_parsed_from_function(self): + testcases = [ + { + "config": { + "name": "basic test with httpbin", + "base_url": HTTPBIN_SERVER, + "variables": "${gen_variables()}" + }, + "teststeps": [ + { + "name": "modify request headers", + "base_url": HTTPBIN_SERVER, + "request": { + "url": "/anything", + "method": "POST", + "headers": { + "user_agent": "iOS/10.3", + "os_platform": "ios" + }, + "data": "a=1&b=2" + }, + "validate": [ + {"check": "status_code", "expect": 200} + ] + } + ] + } + ] + tests_mapping = { + "project_mapping": { + "functions": self.debugtalk_functions + }, + "testcases": testcases + } + parsed_testcases = parser.parse_tests(tests_mapping) + parsed_testcase = parsed_testcases[0] + test_runner = runner.Runner(parsed_testcase["config"]) + test_runner.run_test(parsed_testcase["teststeps"][0]) + test_variables_mapping = test_runner.session_context.test_variables_mapping + self.assertEqual(test_variables_mapping["var_a"], 1) + self.assertEqual(test_variables_mapping["var_b"], 2) + self.assertEqual(test_variables_mapping["request"]["data"], "a=1&b=2")