From 22066da9f08826eaec7ab1c6e1fcae1ee577f722 Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 9 Aug 2018 10:33:57 +0800 Subject: [PATCH] relocate TestcaseParser --- httprunner/context.py | 281 +++++++++++++++++++++++++++++++++++- httprunner/parser.py | 282 +----------------------------------- httprunner/task.py | 6 +- tests/test_context.py | 329 +++++++++++++++++++++++++++++++++++++++++- tests/test_parser.py | 321 ----------------------------------------- tests/test_utils.py | 2 +- 6 files changed, 610 insertions(+), 611 deletions(-) diff --git a/httprunner/context.py b/httprunner/context.py index 2f7f1045..f812b7e7 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -2,11 +2,288 @@ import copy import os +import random import re import sys from httprunner import built_in, exceptions, loader, logger, parser, utils -from httprunner.compat import OrderedDict +from httprunner.compat import OrderedDict, basestring, builtin_str, str + + +def parse_parameters(parameters, testset_path=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()}" + + testset_path (str): testset file path, used for locating csv file and 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) + + """ + testcase_parser = TestcaseParser(file_path=testset_path) + + parsed_parameters_list = [] + for parameter in parameters: + parameter_name, parameter_content = list(parameter.items())[0] + 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: + # (2) & (3) + parsed_parameter_content = testcase_parser.eval_content_with_bindings(parameter_content) + # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] + # e.g. [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}] + if not isinstance(parsed_parameter_content, list): + raise exceptions.ParamsError("parameters syntax error!") + + parameter_content_list = [ + # get subset by parameter name + {key: parameter_item[key] for key in parameter_name_list} + for parameter_item in parsed_parameter_content + ] + + parsed_parameters_list.append(parameter_content_list) + + return utils.gen_cartesian_product(*parsed_parameters_list) + + +class TestcaseParser(object): + + def __init__(self, variables={}, functions={}, file_path=None): + self.update_binded_variables(variables) + self.bind_functions(functions) + self.file_path = file_path + + def update_binded_variables(self, variables): + """ bind variables to current testcase parser + @param (dict) variables, variables binds mapping + { + "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", + "random": "A2dEx", + "data": {"name": "user", "password": "123456"}, + "uuid": 1000 + } + """ + self.variables = variables + + def bind_functions(self, functions): + """ bind functions to current testcase parser + @param (dict) functions, functions binds mapping + { + "add_two_nums": lambda a, b=1: a + b + } + """ + self.functions = functions + + def _get_bind_item(self, item_type, item_name): + """ get specified function or variable. + + Args: + item_type(str): functions or variables + item_name(str): function name or variable name + + Returns: + object: specified function or variable object. + """ + if item_type == "functions": + if item_name in self.functions: + return self.functions[item_name] + + try: + # check if builtin functions + item_func = eval(item_name) + if callable(item_func): + # is builtin function + return item_func + except (NameError, TypeError): + # is not builtin function, continue to search + pass + else: + # item_type == "variables": + if item_name in self.variables: + return self.variables[item_name] + + debugtalk_module = loader.load_debugtalk_module(self.file_path) + return loader.get_module_item(debugtalk_module, item_type, item_name) + + def get_bind_function(self, func_name): + return self._get_bind_item("functions", func_name) + + def get_bind_variable(self, variable_name): + return self._get_bind_item("variables", variable_name) + + def load_csv_list(self, csv_file_name, fetch_method="Sequential"): + """ locate csv file and load csv content. + + Args: + csv_file_name (str): csv file name + fetch_method (str): fetch data method, defaults to Sequential. + If set to "random", csv data list will be reordered in random. + + Returns: + list: csv data list + """ + csv_file_path = loader.locate_file(self.file_path, csv_file_name) + csv_content_list = loader.load_file(csv_file_path) + + if fetch_method.lower() == "random": + random.shuffle(csv_content_list) + + return csv_content_list + + def _eval_content_functions(self, content): + functions_list = parser.extract_functions(content) + for func_content in functions_list: + function_meta = parser.parse_function(func_content) + func_name = function_meta['func_name'] + + args = function_meta.get('args', []) + kwargs = function_meta.get('kwargs', {}) + args = self.eval_content_with_bindings(args) + kwargs = self.eval_content_with_bindings(kwargs) + + if func_name in ["parameterize", "P"]: + eval_value = self.load_csv_list(*args, **kwargs) + else: + func = self.get_bind_function(func_name) + eval_value = func(*args, **kwargs) + + func_content = "${" + func_content + "}" + if func_content == content: + # content is a variable + content = eval_value + else: + # content contains one or many variables + content = content.replace( + func_content, + str(eval_value), 1 + ) + + return content + + def _eval_content_variables(self, content): + """ replace all variables of string content with mapping value. + @param (str) content + @return (str) parsed content + + e.g. + variable_mapping = { + "var_1": "abc", + "var_2": "def" + } + $var_1 => "abc" + $var_1#XYZ => "abc#XYZ" + /$var_1/$var_2/var3 => "/abc/def/var3" + ${func($var_1, $var_2, xyz)} => "${func(abc, def, xyz)}" + """ + variables_list = parser.extract_variables(content) + for variable_name in variables_list: + variable_value = self.get_bind_variable(variable_name) + + if "${}".format(variable_name) == content: + # content is a variable + content = variable_value + else: + # content contains one or several variables + if not isinstance(variable_value, str): + variable_value = builtin_str(variable_value) + + content = content.replace( + "${}".format(variable_name), + variable_value, 1 + ) + + return content + + def eval_content_with_bindings(self, content): + """ parse content recursively, each variable and function in content will be evaluated. + + @param (dict) content in any data structure + { + "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1, 1)}", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "authorization": "$authorization", + "random": "$random", + "sum": "${add_two_nums(1, 2)}" + }, + "body": "$data" + } + @return (dict) parsed content with evaluated bind values + { + "url": "http://127.0.0.1:5000/api/users/1000/2", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", + "random": "A2dEx", + "sum": 3 + }, + "body": {"name": "user", "password": "123456"} + } + """ + if content is None: + return None + + if isinstance(content, (list, tuple)): + return [ + self.eval_content_with_bindings(item) + for item in content + ] + + if isinstance(content, dict): + evaluated_data = {} + for key, value in content.items(): + eval_key = self.eval_content_with_bindings(key) + eval_value = self.eval_content_with_bindings(value) + evaluated_data[eval_key] = eval_value + + return evaluated_data + + if isinstance(content, basestring): + + # content is in string format here + content = content.strip() + + # replace functions with evaluated value + # Notice: _eval_content_functions must be called before _eval_content_variables + content = self._eval_content_functions(content) + + # replace variables with binding value + content = self._eval_content_variables(content) + + return content class Context(object): @@ -16,7 +293,7 @@ class Context(object): def __init__(self): self.testset_shared_variables_mapping = OrderedDict() self.testcase_variables_mapping = OrderedDict() - self.testcase_parser = parser.TestcaseParser() + self.testcase_parser = TestcaseParser() self.evaluated_validators = [] self.init_context() diff --git a/httprunner/parser.py b/httprunner/parser.py index 9ff00401..0994c047 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -2,12 +2,10 @@ import ast import os -import random import re -from httprunner import exceptions, loader, utils -from httprunner.compat import (OrderedDict, basestring, builtin_str, - numeric_types, str) +from httprunner import exceptions +from httprunner.compat import builtin_str, numeric_types, str variable_regexp = r"\$([\w_]+)" function_regexp = r"\$\{([\w_]+\([\$\w\.\-_ =,]*\))\}" @@ -252,279 +250,3 @@ def parse_data(content, mapping): content = content.replace(var, value) return content - - -def parse_parameters(parameters, testset_path=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()}" - - testset_path (str): testset file path, used for locating csv file and 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) - - """ - testcase_parser = TestcaseParser(file_path=testset_path) - - parsed_parameters_list = [] - for parameter in parameters: - parameter_name, parameter_content = list(parameter.items())[0] - 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: - # (2) & (3) - parsed_parameter_content = testcase_parser.eval_content_with_bindings(parameter_content) - # e.g. [{'app_version': '2.8.5'}, {'app_version': '2.8.6'}] - # e.g. [{"username": "user1", "password": "111111"}, {"username": "user2", "password": "222222"}] - if not isinstance(parsed_parameter_content, list): - raise exceptions.ParamsError("parameters syntax error!") - - parameter_content_list = [ - # get subset by parameter name - {key: parameter_item[key] for key in parameter_name_list} - for parameter_item in parsed_parameter_content - ] - - parsed_parameters_list.append(parameter_content_list) - - return utils.gen_cartesian_product(*parsed_parameters_list) - - -class TestcaseParser(object): - - def __init__(self, variables={}, functions={}, file_path=None): - self.update_binded_variables(variables) - self.bind_functions(functions) - self.file_path = file_path - - def update_binded_variables(self, variables): - """ bind variables to current testcase parser - @param (dict) variables, variables binds mapping - { - "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", - "random": "A2dEx", - "data": {"name": "user", "password": "123456"}, - "uuid": 1000 - } - """ - self.variables = variables - - def bind_functions(self, functions): - """ bind functions to current testcase parser - @param (dict) functions, functions binds mapping - { - "add_two_nums": lambda a, b=1: a + b - } - """ - self.functions = functions - - def _get_bind_item(self, item_type, item_name): - """ get specified function or variable. - - Args: - item_type(str): functions or variables - item_name(str): function name or variable name - - Returns: - object: specified function or variable object. - """ - if item_type == "functions": - if item_name in self.functions: - return self.functions[item_name] - - try: - # check if builtin functions - item_func = eval(item_name) - if callable(item_func): - # is builtin function - return item_func - except (NameError, TypeError): - # is not builtin function, continue to search - pass - else: - # item_type == "variables": - if item_name in self.variables: - return self.variables[item_name] - - debugtalk_module = loader.load_debugtalk_module(self.file_path) - return loader.get_module_item(debugtalk_module, item_type, item_name) - - def get_bind_function(self, func_name): - return self._get_bind_item("functions", func_name) - - def get_bind_variable(self, variable_name): - return self._get_bind_item("variables", variable_name) - - def load_csv_list(self, csv_file_name, fetch_method="Sequential"): - """ locate csv file and load csv content. - - Args: - csv_file_name (str): csv file name - fetch_method (str): fetch data method, defaults to Sequential. - If set to "random", csv data list will be reordered in random. - - Returns: - list: csv data list - """ - csv_file_path = loader.locate_file(self.file_path, csv_file_name) - csv_content_list = loader.load_file(csv_file_path) - - if fetch_method.lower() == "random": - random.shuffle(csv_content_list) - - return csv_content_list - - def _eval_content_functions(self, content): - functions_list = extract_functions(content) - for func_content in functions_list: - function_meta = parse_function(func_content) - func_name = function_meta['func_name'] - - args = function_meta.get('args', []) - kwargs = function_meta.get('kwargs', {}) - args = self.eval_content_with_bindings(args) - kwargs = self.eval_content_with_bindings(kwargs) - - if func_name in ["parameterize", "P"]: - eval_value = self.load_csv_list(*args, **kwargs) - else: - func = self.get_bind_function(func_name) - eval_value = func(*args, **kwargs) - - func_content = "${" + func_content + "}" - if func_content == content: - # content is a variable - content = eval_value - else: - # content contains one or many variables - content = content.replace( - func_content, - str(eval_value), 1 - ) - - return content - - def _eval_content_variables(self, content): - """ replace all variables of string content with mapping value. - @param (str) content - @return (str) parsed content - - e.g. - variable_mapping = { - "var_1": "abc", - "var_2": "def" - } - $var_1 => "abc" - $var_1#XYZ => "abc#XYZ" - /$var_1/$var_2/var3 => "/abc/def/var3" - ${func($var_1, $var_2, xyz)} => "${func(abc, def, xyz)}" - """ - variables_list = extract_variables(content) - for variable_name in variables_list: - variable_value = self.get_bind_variable(variable_name) - - if "${}".format(variable_name) == content: - # content is a variable - content = variable_value - else: - # content contains one or several variables - if not isinstance(variable_value, str): - variable_value = builtin_str(variable_value) - - content = content.replace( - "${}".format(variable_name), - variable_value, 1 - ) - - return content - - def eval_content_with_bindings(self, content): - """ parse content recursively, each variable and function in content will be evaluated. - - @param (dict) content in any data structure - { - "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1, 1)}", - "method": "POST", - "headers": { - "Content-Type": "application/json", - "authorization": "$authorization", - "random": "$random", - "sum": "${add_two_nums(1, 2)}" - }, - "body": "$data" - } - @return (dict) parsed content with evaluated bind values - { - "url": "http://127.0.0.1:5000/api/users/1000/2", - "method": "POST", - "headers": { - "Content-Type": "application/json", - "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", - "random": "A2dEx", - "sum": 3 - }, - "body": {"name": "user", "password": "123456"} - } - """ - if content is None: - return None - - if isinstance(content, (list, tuple)): - return [ - self.eval_content_with_bindings(item) - for item in content - ] - - if isinstance(content, dict): - evaluated_data = {} - for key, value in content.items(): - eval_key = self.eval_content_with_bindings(key) - eval_value = self.eval_content_with_bindings(value) - evaluated_data[eval_key] = eval_value - - return evaluated_data - - if isinstance(content, basestring): - - # content is in string format here - content = content.strip() - - # replace functions with evaluated value - # Notice: _eval_content_functions must be called before _eval_content_variables - content = self._eval_content_functions(content) - - # replace variables with binding value - content = self._eval_content_variables(content) - - return content diff --git a/httprunner/task.py b/httprunner/task.py index 3b353a48..76746552 100644 --- a/httprunner/task.py +++ b/httprunner/task.py @@ -4,7 +4,7 @@ import copy import sys import unittest -from httprunner import exceptions, loader, logger, parser, runner, utils +from httprunner import context, exceptions, loader, logger, runner, utils from httprunner.compat import is_py3 from httprunner.report import (HtmlTestResult, get_platform, get_summary, render_html_report) @@ -78,7 +78,7 @@ class TestSuite(unittest.TestSuite): config_dict_variables, config_dict_parameters ) - self.testcase_parser = parser.TestcaseParser() + self.testcase_parser = context.TestcaseParser() testcases = testset.get("testcases", []) for config_variables in config_parametered_variables_list: @@ -114,7 +114,7 @@ class TestSuite(unittest.TestSuite): def _get_parametered_variables(self, variables, parameters): """ parameterize varaibles with parameters """ - cartesian_product_parameters = parser.parse_parameters( + cartesian_product_parameters = context.parse_parameters( parameters, self.testset_file_path ) or [{}] diff --git a/tests/test_context.py b/tests/test_context.py index cd00e69e..d28351a9 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -1,17 +1,17 @@ import os import time +import unittest import requests -from httprunner import exceptions, loader, response, runner -from httprunner.context import Context +from httprunner import context, exceptions, loader, parser, response, runner from httprunner.utils import gen_md5 from tests.base import ApiServerUnittest -class VariableBindsUnittest(ApiServerUnittest): +class TestContext(ApiServerUnittest): def setUp(self): - self.context = Context() + self.context = context.Context() testcase_file_path = os.path.join(os.getcwd(), 'tests/data/demo_binds.yml') self.testcases = loader.load_file(testcase_file_path) @@ -261,3 +261,324 @@ class VariableBindsUnittest(ApiServerUnittest): with self.assertRaises(exceptions.ValidationFailure): self.context.validate(validators, resp_obj) + + +class TestTestcaseParser(unittest.TestCase): + + def test_eval_content_variables(self): + variables = { + "var_1": "abc", + "var_2": "def", + "var_3": 123, + "var_4": {"a": 1}, + "var_5": True, + "var_6": None + } + testcase_parser = context.TestcaseParser(variables=variables) + self.assertEqual( + testcase_parser._eval_content_variables("$var_1"), + "abc" + ) + self.assertEqual( + testcase_parser._eval_content_variables("var_1"), + "var_1" + ) + self.assertEqual( + testcase_parser._eval_content_variables("$var_1#XYZ"), + "abc#XYZ" + ) + self.assertEqual( + testcase_parser._eval_content_variables("/$var_1/$var_2/var3"), + "/abc/def/var3" + ) + self.assertEqual( + testcase_parser._eval_content_variables("/$var_1/$var_2/$var_1"), + "/abc/def/abc" + ) + self.assertEqual( + testcase_parser._eval_content_variables("${func($var_1, $var_2, xyz)}"), + "${func(abc, def, xyz)}" + ) + self.assertEqual( + testcase_parser._eval_content_variables("$var_3"), + 123 + ) + self.assertEqual( + testcase_parser._eval_content_variables("$var_4"), + {"a": 1} + ) + self.assertEqual( + testcase_parser._eval_content_variables("$var_5"), + True + ) + self.assertEqual( + testcase_parser._eval_content_variables("abc$var_5"), + "abcTrue" + ) + self.assertEqual( + testcase_parser._eval_content_variables("abc$var_4"), + "abc{'a': 1}" + ) + self.assertEqual( + testcase_parser._eval_content_variables("$var_6"), + None + ) + + def test_eval_content_variables_search_upward(self): + testcase_parser = context.TestcaseParser() + + with self.assertRaises(exceptions.VariableNotFound): + testcase_parser._eval_content_variables("/api/$SECRET_KEY") + + testcase_parser.file_path = "tests/data/demo_testset_hardcode.yml" + content = testcase_parser._eval_content_variables("/api/$SECRET_KEY") + self.assertEqual(content, "/api/DebugTalk") + + + def test_parse_content_with_bindings_variables(self): + variables = { + "str_1": "str_value1", + "str_2": "str_value2" + } + testcase_parser = context.TestcaseParser(variables=variables) + self.assertEqual( + testcase_parser.eval_content_with_bindings("$str_1"), + "str_value1" + ) + self.assertEqual( + testcase_parser.eval_content_with_bindings("123$str_1/456"), + "123str_value1/456" + ) + + with self.assertRaises(exceptions.VariableNotFound): + testcase_parser.eval_content_with_bindings("$str_3") + + self.assertEqual( + testcase_parser.eval_content_with_bindings(["$str_1", "str3"]), + ["str_value1", "str3"] + ) + self.assertEqual( + testcase_parser.eval_content_with_bindings({"key": "$str_1"}), + {"key": "str_value1"} + ) + + def test_parse_content_with_bindings_multiple_identical_variables(self): + variables = { + "userid": 100, + "data": 1498 + } + testcase_parser = context.TestcaseParser(variables=variables) + content = "/users/$userid/training/$data?userId=$userid&data=$data" + self.assertEqual( + testcase_parser.eval_content_with_bindings(content), + "/users/100/training/1498?userId=100&data=1498" + ) + + def test_parse_variables_multiple_identical_variables(self): + variables = { + "user": 100, + "userid": 1000, + "data": 1498 + } + testcase_parser = context.TestcaseParser(variables=variables) + content = "/users/$user/$userid/$data?userId=$userid&data=$data" + self.assertEqual( + testcase_parser.eval_content_with_bindings(content), + "/users/100/1000/1498?userId=1000&data=1498" + ) + + def test_parse_content_with_bindings_functions(self): + import random, string + functions = { + "gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \ + for _ in range(str_len)) + } + testcase_parser = context.TestcaseParser(functions=functions) + + result = testcase_parser.eval_content_with_bindings("${gen_random_string(5)}") + self.assertEqual(len(result), 5) + + add_two_nums = lambda a, b=1: a + b + functions["add_two_nums"] = add_two_nums + self.assertEqual( + testcase_parser.eval_content_with_bindings("${add_two_nums(1)}"), + 2 + ) + self.assertEqual( + testcase_parser.eval_content_with_bindings("${add_two_nums(1, 2)}"), + 3 + ) + + def test_extract_functions(self): + self.assertEqual( + parser.extract_functions("${func()}"), + ["func()"] + ) + self.assertEqual( + parser.extract_functions("${func(5)}"), + ["func(5)"] + ) + self.assertEqual( + parser.extract_functions("${func(a=1, b=2)}"), + ["func(a=1, b=2)"] + ) + self.assertEqual( + parser.extract_functions("${func(1, $b, c=$x, d=4)}"), + ["func(1, $b, c=$x, d=4)"] + ) + self.assertEqual( + parser.extract_functions("/api/1000?_t=${get_timestamp()}"), + ["get_timestamp()"] + ) + self.assertEqual( + parser.extract_functions("/api/${add(1, 2)}"), + ["add(1, 2)"] + ) + self.assertEqual( + parser.extract_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), + ["add(1, 2)", "get_timestamp()"] + ) + self.assertEqual( + parser.extract_functions("abc${func(1, 2, a=3, b=4)}def"), + ["func(1, 2, a=3, b=4)"] + ) + + def test_eval_content_functions(self): + functions = { + "add_two_nums": lambda a, b=1: a + b + } + testcase_parser = context.TestcaseParser(functions=functions) + self.assertEqual( + testcase_parser._eval_content_functions("${add_two_nums(1, 2)}"), + 3 + ) + self.assertEqual( + testcase_parser._eval_content_functions("/api/${add_two_nums(1, 2)}"), + "/api/3" + ) + + def test_eval_content_functions_search_upward(self): + testcase_parser = context.TestcaseParser() + + with self.assertRaises(exceptions.FunctionNotFound): + testcase_parser._eval_content_functions("/api/${gen_md5(abc)}") + + testcase_parser.file_path = "tests/data/demo_testset_hardcode.yml" + content = testcase_parser._eval_content_functions("/api/${gen_md5(abc)}") + self.assertEqual(content, "/api/900150983cd24fb0d6963f7d28e17f72") + + def test_parse_content_with_bindings_testcase(self): + variables = { + "uid": "1000", + "random": "A2dEx", + "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", + "data": {"name": "user", "password": "123456"} + } + functions = { + "add_two_nums": lambda a, b=1: a + b, + "get_timestamp": lambda: int(time.time() * 1000) + } + testcase_template = { + "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}", + "method": "POST", + "headers": { + "Content-Type": "application/json", + "authorization": "$authorization", + "random": "$random", + "sum": "${add_two_nums(1, 2)}" + }, + "body": "$data" + } + parsed_testcase = context.TestcaseParser(variables, functions)\ + .eval_content_with_bindings(testcase_template) + + self.assertEqual( + parsed_testcase["url"], + "http://127.0.0.1:5000/api/users/1000/3" + ) + self.assertEqual( + parsed_testcase["headers"]["authorization"], + variables["authorization"] + ) + self.assertEqual( + parsed_testcase["headers"]["random"], + variables["random"] + ) + self.assertEqual( + parsed_testcase["body"], + variables["data"] + ) + self.assertEqual( + parsed_testcase["headers"]["sum"], + 3 + ) + + def test_parse_parameters_raw_list(self): + parameters = [ + {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, + {"username-password": [("user1", "111111"), ["test2", "222222"]]} + ] + cartesian_product_parameters = context.parse_parameters(parameters) + self.assertEqual( + len(cartesian_product_parameters), + 3 * 2 + ) + self.assertEqual( + cartesian_product_parameters[0], + {'user_agent': 'iOS/10.1', 'username': 'user1', 'password': '111111'} + ) + + def test_parse_parameters_parameterize(self): + parameters = [ + {"app_version": "${parameterize(app_version.csv)}"}, + {"username-password": "${parameterize(account.csv)}"} + ] + testset_path = os.path.join( + os.getcwd(), + "tests/data/demo_parameters.yml" + ) + cartesian_product_parameters = context.parse_parameters( + parameters, + testset_path + ) + self.assertEqual( + len(cartesian_product_parameters), + 2 * 3 + ) + + def test_parse_parameters_custom_function(self): + parameters = [ + {"app_version": "${gen_app_version()}"}, + {"username-password": "${get_account()}"} + ] + testset_path = os.path.join( + os.getcwd(), + "tests/data/demo_parameters.yml" + ) + cartesian_product_parameters = context.parse_parameters( + parameters, + testset_path + ) + self.assertEqual( + len(cartesian_product_parameters), + 2 * 2 + ) + + def test_parse_parameters_mix(self): + parameters = [ + {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, + {"app_version": "${gen_app_version()}"}, + {"username-password": "${parameterize(account.csv)}"} + ] + testset_path = os.path.join( + os.getcwd(), + "tests/data/demo_parameters.yml" + ) + cartesian_product_parameters = context.parse_parameters( + parameters, + testset_path + ) + self.assertEqual( + len(cartesian_product_parameters), + 3 * 2 * 3 + ) diff --git a/tests/test_parser.py b/tests/test_parser.py index 8a409628..41bd7145 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -115,76 +115,6 @@ class TestParser(unittest.TestCase): {"check": "status_code", "comparator": "eq", "expect": 201} ) - def test_parse_parameters_raw_list(self): - parameters = [ - {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, - {"username-password": [("user1", "111111"), ["test2", "222222"]]} - ] - cartesian_product_parameters = parser.parse_parameters(parameters) - self.assertEqual( - len(cartesian_product_parameters), - 3 * 2 - ) - self.assertEqual( - cartesian_product_parameters[0], - {'user_agent': 'iOS/10.1', 'username': 'user1', 'password': '111111'} - ) - - def test_parse_parameters_parameterize(self): - parameters = [ - {"app_version": "${parameterize(app_version.csv)}"}, - {"username-password": "${parameterize(account.csv)}"} - ] - testset_path = os.path.join( - os.getcwd(), - "tests/data/demo_parameters.yml" - ) - cartesian_product_parameters = parser.parse_parameters( - parameters, - testset_path - ) - self.assertEqual( - len(cartesian_product_parameters), - 2 * 3 - ) - - def test_parse_parameters_custom_function(self): - parameters = [ - {"app_version": "${gen_app_version()}"}, - {"username-password": "${get_account()}"} - ] - testset_path = os.path.join( - os.getcwd(), - "tests/data/demo_parameters.yml" - ) - cartesian_product_parameters = parser.parse_parameters( - parameters, - testset_path - ) - self.assertEqual( - len(cartesian_product_parameters), - 2 * 2 - ) - - def test_parse_parameters_mix(self): - parameters = [ - {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, - {"app_version": "${gen_app_version()}"}, - {"username-password": "${parameterize(account.csv)}"} - ] - testset_path = os.path.join( - os.getcwd(), - "tests/data/demo_parameters.yml" - ) - cartesian_product_parameters = parser.parse_parameters( - parameters, - testset_path - ) - self.assertEqual( - len(cartesian_product_parameters), - 3 * 2 * 3 - ) - def test_parse_data(self): content = { 'request': { @@ -211,254 +141,3 @@ class TestParser(unittest.TestCase): self.assertTrue(result["request"]["data"]["true"]) self.assertFalse(result["request"]["data"]["false"]) self.assertEqual("", result["request"]["data"]["empty_str"]) - - -class TestTestcaseParser(unittest.TestCase): - - def test_eval_content_variables(self): - variables = { - "var_1": "abc", - "var_2": "def", - "var_3": 123, - "var_4": {"a": 1}, - "var_5": True, - "var_6": None - } - testcase_parser = parser.TestcaseParser(variables=variables) - self.assertEqual( - testcase_parser._eval_content_variables("$var_1"), - "abc" - ) - self.assertEqual( - testcase_parser._eval_content_variables("var_1"), - "var_1" - ) - self.assertEqual( - testcase_parser._eval_content_variables("$var_1#XYZ"), - "abc#XYZ" - ) - self.assertEqual( - testcase_parser._eval_content_variables("/$var_1/$var_2/var3"), - "/abc/def/var3" - ) - self.assertEqual( - testcase_parser._eval_content_variables("/$var_1/$var_2/$var_1"), - "/abc/def/abc" - ) - self.assertEqual( - testcase_parser._eval_content_variables("${func($var_1, $var_2, xyz)}"), - "${func(abc, def, xyz)}" - ) - self.assertEqual( - testcase_parser._eval_content_variables("$var_3"), - 123 - ) - self.assertEqual( - testcase_parser._eval_content_variables("$var_4"), - {"a": 1} - ) - self.assertEqual( - testcase_parser._eval_content_variables("$var_5"), - True - ) - self.assertEqual( - testcase_parser._eval_content_variables("abc$var_5"), - "abcTrue" - ) - self.assertEqual( - testcase_parser._eval_content_variables("abc$var_4"), - "abc{'a': 1}" - ) - self.assertEqual( - testcase_parser._eval_content_variables("$var_6"), - None - ) - - def test_eval_content_variables_search_upward(self): - testcase_parser = parser.TestcaseParser() - - with self.assertRaises(exceptions.VariableNotFound): - testcase_parser._eval_content_variables("/api/$SECRET_KEY") - - testcase_parser.file_path = "tests/data/demo_testset_hardcode.yml" - content = testcase_parser._eval_content_variables("/api/$SECRET_KEY") - self.assertEqual(content, "/api/DebugTalk") - - - def test_parse_content_with_bindings_variables(self): - variables = { - "str_1": "str_value1", - "str_2": "str_value2" - } - testcase_parser = parser.TestcaseParser(variables=variables) - self.assertEqual( - testcase_parser.eval_content_with_bindings("$str_1"), - "str_value1" - ) - self.assertEqual( - testcase_parser.eval_content_with_bindings("123$str_1/456"), - "123str_value1/456" - ) - - with self.assertRaises(exceptions.VariableNotFound): - testcase_parser.eval_content_with_bindings("$str_3") - - self.assertEqual( - testcase_parser.eval_content_with_bindings(["$str_1", "str3"]), - ["str_value1", "str3"] - ) - self.assertEqual( - testcase_parser.eval_content_with_bindings({"key": "$str_1"}), - {"key": "str_value1"} - ) - - def test_parse_content_with_bindings_multiple_identical_variables(self): - variables = { - "userid": 100, - "data": 1498 - } - testcase_parser = parser.TestcaseParser(variables=variables) - content = "/users/$userid/training/$data?userId=$userid&data=$data" - self.assertEqual( - testcase_parser.eval_content_with_bindings(content), - "/users/100/training/1498?userId=100&data=1498" - ) - - def test_parse_variables_multiple_identical_variables(self): - variables = { - "user": 100, - "userid": 1000, - "data": 1498 - } - testcase_parser = parser.TestcaseParser(variables=variables) - content = "/users/$user/$userid/$data?userId=$userid&data=$data" - self.assertEqual( - testcase_parser.eval_content_with_bindings(content), - "/users/100/1000/1498?userId=1000&data=1498" - ) - - def test_parse_content_with_bindings_functions(self): - import random, string - functions = { - "gen_random_string": lambda str_len: ''.join(random.choice(string.ascii_letters + string.digits) \ - for _ in range(str_len)) - } - testcase_parser = parser.TestcaseParser(functions=functions) - - result = testcase_parser.eval_content_with_bindings("${gen_random_string(5)}") - self.assertEqual(len(result), 5) - - add_two_nums = lambda a, b=1: a + b - functions["add_two_nums"] = add_two_nums - self.assertEqual( - testcase_parser.eval_content_with_bindings("${add_two_nums(1)}"), - 2 - ) - self.assertEqual( - testcase_parser.eval_content_with_bindings("${add_two_nums(1, 2)}"), - 3 - ) - - def test_extract_functions(self): - self.assertEqual( - parser.extract_functions("${func()}"), - ["func()"] - ) - self.assertEqual( - parser.extract_functions("${func(5)}"), - ["func(5)"] - ) - self.assertEqual( - parser.extract_functions("${func(a=1, b=2)}"), - ["func(a=1, b=2)"] - ) - self.assertEqual( - parser.extract_functions("${func(1, $b, c=$x, d=4)}"), - ["func(1, $b, c=$x, d=4)"] - ) - self.assertEqual( - parser.extract_functions("/api/1000?_t=${get_timestamp()}"), - ["get_timestamp()"] - ) - self.assertEqual( - parser.extract_functions("/api/${add(1, 2)}"), - ["add(1, 2)"] - ) - self.assertEqual( - parser.extract_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), - ["add(1, 2)", "get_timestamp()"] - ) - self.assertEqual( - parser.extract_functions("abc${func(1, 2, a=3, b=4)}def"), - ["func(1, 2, a=3, b=4)"] - ) - - def test_eval_content_functions(self): - functions = { - "add_two_nums": lambda a, b=1: a + b - } - testcase_parser = parser.TestcaseParser(functions=functions) - self.assertEqual( - testcase_parser._eval_content_functions("${add_two_nums(1, 2)}"), - 3 - ) - self.assertEqual( - testcase_parser._eval_content_functions("/api/${add_two_nums(1, 2)}"), - "/api/3" - ) - - def test_eval_content_functions_search_upward(self): - testcase_parser = parser.TestcaseParser() - - with self.assertRaises(exceptions.FunctionNotFound): - testcase_parser._eval_content_functions("/api/${gen_md5(abc)}") - - testcase_parser.file_path = "tests/data/demo_testset_hardcode.yml" - content = testcase_parser._eval_content_functions("/api/${gen_md5(abc)}") - self.assertEqual(content, "/api/900150983cd24fb0d6963f7d28e17f72") - - def test_parse_content_with_bindings_testcase(self): - variables = { - "uid": "1000", - "random": "A2dEx", - "authorization": "a83de0ff8d2e896dbd8efb81ba14e17d", - "data": {"name": "user", "password": "123456"} - } - functions = { - "add_two_nums": lambda a, b=1: a + b, - "get_timestamp": lambda: int(time.time() * 1000) - } - testcase_template = { - "url": "http://127.0.0.1:5000/api/users/$uid/${add_two_nums(1,2)}", - "method": "POST", - "headers": { - "Content-Type": "application/json", - "authorization": "$authorization", - "random": "$random", - "sum": "${add_two_nums(1, 2)}" - }, - "body": "$data" - } - parsed_testcase = parser.TestcaseParser(variables, functions)\ - .eval_content_with_bindings(testcase_template) - - self.assertEqual( - parsed_testcase["url"], - "http://127.0.0.1:5000/api/users/1000/3" - ) - self.assertEqual( - parsed_testcase["headers"]["authorization"], - variables["authorization"] - ) - self.assertEqual( - parsed_testcase["headers"]["random"], - variables["random"] - ) - self.assertEqual( - parsed_testcase["body"], - variables["data"] - ) - self.assertEqual( - parsed_testcase["headers"]["sum"], - 3 - ) diff --git a/tests/test_utils.py b/tests/test_utils.py index 35059814..404a262e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,7 @@ import os import shutil -from httprunner import exceptions, loader, utils, validator +from httprunner import exceptions, loader, utils from httprunner.compat import OrderedDict from tests.base import ApiServerUnittest