From 243515a2ec7c3e730a7f934a8f542b18b5da953b Mon Sep 17 00:00:00 2001 From: debugtalk Date: Sun, 5 Aug 2018 11:46:27 +0800 Subject: [PATCH] relocate testcase parser --- httprunner/__about__.py | 2 +- httprunner/context.py | 6 +- httprunner/parser.py | 284 +++++++++++++++++++++++++++++- httprunner/response.py | 2 +- httprunner/task.py | 6 +- httprunner/testcase.py | 322 ---------------------------------- httprunner/utils.py | 35 ++++ tests/test_context.py | 2 +- tests/test_parser.py | 325 ++++++++++++++++++++++++++++++++++- tests/test_testcase.py | 370 ---------------------------------------- tests/test_utils.py | 43 +++++ 11 files changed, 694 insertions(+), 703 deletions(-) delete mode 100644 httprunner/testcase.py delete mode 100644 tests/test_testcase.py diff --git a/httprunner/__about__.py b/httprunner/__about__.py index c146d7ef..e36b505f 100644 --- a/httprunner/__about__.py +++ b/httprunner/__about__.py @@ -1,7 +1,7 @@ __title__ = 'HttpRunner' __description__ = 'One-stop solution for HTTP(S) testing.' __url__ = 'https://github.com/HttpRunner/HttpRunner' -__version__ = '1.5.9' +__version__ = '1.5.10' __author__ = 'debugtalk' __author_email__ = 'mail@debugtalk.com' __license__ = 'MIT' diff --git a/httprunner/context.py b/httprunner/context.py index 35d9c8a5..fc1134ce 100644 --- a/httprunner/context.py +++ b/httprunner/context.py @@ -5,7 +5,7 @@ import os import re import sys -from httprunner import built_in, exceptions, logger, parser, testcase, utils +from httprunner import built_in, exceptions, logger, parser, utils from httprunner.compat import OrderedDict @@ -16,7 +16,7 @@ class Context(object): def __init__(self): self.testset_shared_variables_mapping = OrderedDict() self.testcase_variables_mapping = OrderedDict() - self.testcase_parser = testcase.TestcaseParser() + self.testcase_parser = parser.TestcaseParser() self.evaluated_validators = [] self.init_context() @@ -178,7 +178,7 @@ class Context(object): if isinstance(check_item, (dict, list)) \ or parser.extract_variables(check_item) \ - or testcase.extract_functions(check_item): + or parser.extract_functions(check_item): # format 1/2/3 check_value = self.eval_content(check_item) else: diff --git a/httprunner/parser.py b/httprunner/parser.py index caa63a80..283ecf9a 100644 --- a/httprunner/parser.py +++ b/httprunner/parser.py @@ -1,8 +1,15 @@ +# encoding: utf-8 + import ast +import os +import random import re -from httprunner import exceptions +from httprunner import exceptions, loader, logger, utils +from httprunner.compat import (OrderedDict, basestring, builtin_str, + numeric_types, str) +function_regexp = r"\$\{([\w_]+\([\$\w\.\-_ =,]*\))\}" variable_regexp = r"\$([\w_]+)" function_regexp_compile = re.compile(r"^([\w_]+)\(([\$\w\.\-_ =,]*)\)$") @@ -127,3 +134,278 @@ def parse_validator(validator): "expect": expect_value, "comparator": comparator } + + +def parse_parameters(parameters, testset_path=None): + """ parse parameters and generate cartesian product + @params + (list) parameters: parameter name and value in list + parameter value may be in three types: + (1) data list + (2) call built-in parameterize function + (3) call custom function in debugtalk.py + e.g. + [ + {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, + {"username-password": "${parameterize(account.csv)}"}, + {"app_version": "${gen_app_version()}"} + ] + (str) testset_path: testset file path, used for locating csv file and debugtalk.py + @return cartesian product in list + """ + 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) + + +def extract_functions(content): + """ extract all functions from string content, which are in format ${fun()} + @param (str) content + @return (list) functions list + + e.g. ${func(5)} => ["func(5)"] + ${func(a=1, b=2)} => ["func(a=1, b=2)"] + /api/1000?_t=${get_timestamp()} => ["get_timestamp()"] + /api/${add(1, 2)} => ["add(1, 2)"] + "/api/${add(1, 2)}?_t=${get_timestamp()}" => ["add(1, 2)", "get_timestamp()"] + """ + try: + return re.findall(function_regexp, content) + except TypeError: + return [] + + +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): + if item_type == "function": + 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 + elif item_type == "variable": + if item_name in self.variables: + return self.variables[item_name] + else: + raise exceptions.ParamsError("bind item should only be function or variable.") + + try: + assert self.file_path is not None + return utils.search_conf_item(self.file_path, item_type, item_name) + except (AssertionError, exceptions.FunctionNotFound): + raise exceptions.ParamsError( + "{} is not defined in bind {}s!".format(item_name, item_type)) + + def get_bind_function(self, func_name): + return self._get_bind_item("function", func_name) + + def get_bind_variable(self, variable_name): + return self._get_bind_item("variable", variable_name) + + def parameterize(self, csv_file_name, fetch_method="Sequential"): + parameter_file_path = os.path.join( + os.path.dirname(self.file_path), + "{}".format(csv_file_name) + ) + csv_content_list = loader.load_file(parameter_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.parameterize(*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/response.py b/httprunner/response.py index 7bfda117..aeb4eaa6 100644 --- a/httprunner/response.py +++ b/httprunner/response.py @@ -3,7 +3,7 @@ import json import re -from httprunner import exceptions, logger, testcase, utils +from httprunner import exceptions, logger, utils from httprunner.compat import OrderedDict, basestring, is_py2 from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict diff --git a/httprunner/task.py b/httprunner/task.py index 964adcbb..54d77bc2 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, runner, testcase, utils +from httprunner import exceptions, loader, logger, parser, 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 = testcase.TestcaseParser() + self.testcase_parser = parser.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 = testcase.parse_parameters( + cartesian_product_parameters = parser.parse_parameters( parameters, self.testset_file_path ) or [{}] diff --git a/httprunner/testcase.py b/httprunner/testcase.py deleted file mode 100644 index b3f7ba8e..00000000 --- a/httprunner/testcase.py +++ /dev/null @@ -1,322 +0,0 @@ -# encoding: utf-8 - -import io -import itertools -import json -import os -import random -import re - -from httprunner import exceptions, loader, logger, parser, utils -from httprunner.compat import (OrderedDict, basestring, builtin_str, - numeric_types, str) - - -function_regexp = r"\$\{([\w_]+\([\$\w\.\-_ =,]*\))\}" - - -def extract_functions(content): - """ extract all functions from string content, which are in format ${fun()} - @param (str) content - @return (list) functions list - - e.g. ${func(5)} => ["func(5)"] - ${func(a=1, b=2)} => ["func(a=1, b=2)"] - /api/1000?_t=${get_timestamp()} => ["get_timestamp()"] - /api/${add(1, 2)} => ["add(1, 2)"] - "/api/${add(1, 2)}?_t=${get_timestamp()}" => ["add(1, 2)", "get_timestamp()"] - """ - try: - return re.findall(function_regexp, content) - except TypeError: - return [] - - -def gen_cartesian_product(*args): - """ generate cartesian product for lists - @param - (list) args - [{"a": 1}, {"a": 2}], - [ - {"x": 111, "y": 112}, - {"x": 121, "y": 122} - ] - @return - cartesian product in list - [ - {'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 - -def parse_parameters(parameters, testset_path=None): - """ parse parameters and generate cartesian product - @params - (list) parameters: parameter name and value in list - parameter value may be in three types: - (1) data list - (2) call built-in parameterize function - (3) call custom function in debugtalk.py - e.g. - [ - {"user_agent": ["iOS/10.1", "iOS/10.2", "iOS/10.3"]}, - {"username-password": "${parameterize(account.csv)}"}, - {"app_version": "${gen_app_version()}"} - ] - (str) testset_path: testset file path, used for locating csv file and debugtalk.py - @return cartesian product in list - """ - 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 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): - if item_type == "function": - 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 - elif item_type == "variable": - if item_name in self.variables: - return self.variables[item_name] - else: - raise exceptions.ParamsError("bind item should only be function or variable.") - - try: - assert self.file_path is not None - return utils.search_conf_item(self.file_path, item_type, item_name) - except (AssertionError, exceptions.FunctionNotFound): - raise exceptions.ParamsError( - "{} is not defined in bind {}s!".format(item_name, item_type)) - - def get_bind_function(self, func_name): - return self._get_bind_item("function", func_name) - - def get_bind_variable(self, variable_name): - return self._get_bind_item("variable", variable_name) - - def parameterize(self, csv_file_name, fetch_method="Sequential"): - parameter_file_path = os.path.join( - os.path.dirname(self.file_path), - "{}".format(csv_file_name) - ) - csv_content_list = loader.load_file(parameter_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 = 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.parameterize(*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 diff --git a/httprunner/utils.py b/httprunner/utils.py index 1d32ecb5..217933a2 100644 --- a/httprunner/utils.py +++ b/httprunner/utils.py @@ -7,6 +7,7 @@ import hmac import imp import importlib import io +import itertools import json import os.path import random @@ -577,6 +578,40 @@ def create_scaffold(project_path): logger.color_print(msg, "BLUE") +def gen_cartesian_product(*args): + """ generate cartesian product for lists + @param + (list) args + [{"a": 1}, {"a": 2}], + [ + {"x": 111, "y": 112}, + {"x": 121, "y": 122} + ] + @return + cartesian product in list + [ + {'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 + + def validate_json_file(file_list): """ validate JSON testset format """ diff --git a/tests/test_context.py b/tests/test_context.py index 0e012f24..74e6a986 100644 --- a/tests/test_context.py +++ b/tests/test_context.py @@ -2,7 +2,7 @@ import os import time import requests -from httprunner import exceptions, loader, response, runner, testcase +from httprunner import exceptions, loader, response, runner from httprunner.context import Context from httprunner.utils import gen_md5 from tests.base import ApiServerUnittest diff --git a/tests/test_parser.py b/tests/test_parser.py index 7589c5c2..13e1390b 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -1,6 +1,8 @@ import os +import time import unittest -from httprunner import parser, exceptions + +from httprunner import exceptions, parser class TestParser(unittest.TestCase): @@ -112,3 +114,324 @@ class TestParser(unittest.TestCase): parser.parse_validator(validator), {"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 + ) + + +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.ParamsError): + 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.ParamsError): + 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.ParamsError): + 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_testcase.py b/tests/test_testcase.py deleted file mode 100644 index f39f8f55..00000000 --- a/tests/test_testcase.py +++ /dev/null @@ -1,370 +0,0 @@ -import os -import time -import unittest - -from httprunner import exceptions, loader, testcase - - -class TestcaseParserUnittest(unittest.TestCase): - - def test_cartesian_product_one(self): - parameters_content_list = [ - [ - {"a": 1}, - {"a": 2} - ] - ] - product_list = testcase.gen_cartesian_product(*parameters_content_list) - self.assertEqual( - product_list, - [ - {"a": 1}, - {"a": 2} - ] - ) - - def test_cartesian_product_multiple(self): - parameters_content_list = [ - [ - {"a": 1}, - {"a": 2} - ], - [ - {"x": 111, "y": 112}, - {"x": 121, "y": 122} - ] - ] - product_list = testcase.gen_cartesian_product(*parameters_content_list) - self.assertEqual( - product_list, - [ - {'a': 1, 'x': 111, 'y': 112}, - {'a': 1, 'x': 121, 'y': 122}, - {'a': 2, 'x': 111, 'y': 112}, - {'a': 2, 'x': 121, 'y': 122} - ] - ) - - def test_cartesian_product_empty(self): - parameters_content_list = [] - product_list = testcase.gen_cartesian_product(*parameters_content_list) - self.assertEqual(product_list, []) - - 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 = testcase.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 = testcase.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 = testcase.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 = testcase.parse_parameters( - parameters, - testset_path - ) - self.assertEqual( - len(cartesian_product_parameters), - 3 * 2 * 3 - ) - - - 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 = testcase.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 = testcase.TestcaseParser() - - with self.assertRaises(exceptions.ParamsError): - 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 = testcase.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.ParamsError): - 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 = testcase.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 = testcase.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 = testcase.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( - testcase.extract_functions("${func()}"), - ["func()"] - ) - self.assertEqual( - testcase.extract_functions("${func(5)}"), - ["func(5)"] - ) - self.assertEqual( - testcase.extract_functions("${func(a=1, b=2)}"), - ["func(a=1, b=2)"] - ) - self.assertEqual( - testcase.extract_functions("${func(1, $b, c=$x, d=4)}"), - ["func(1, $b, c=$x, d=4)"] - ) - self.assertEqual( - testcase.extract_functions("/api/1000?_t=${get_timestamp()}"), - ["get_timestamp()"] - ) - self.assertEqual( - testcase.extract_functions("/api/${add(1, 2)}"), - ["add(1, 2)"] - ) - self.assertEqual( - testcase.extract_functions("/api/${add(1, 2)}?_t=${get_timestamp()}"), - ["add(1, 2)", "get_timestamp()"] - ) - self.assertEqual( - testcase.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 = testcase.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 = testcase.TestcaseParser() - - with self.assertRaises(exceptions.ParamsError): - 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 = testcase.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 bfeef62e..df114c5e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -403,3 +403,46 @@ class TestUtils(ApiServerUnittest): self.assertTrue(os.path.isdir(os.path.join(project_path, "tests", "testcases"))) self.assertTrue(os.path.isfile(os.path.join(project_path, "tests", "debugtalk.py"))) shutil.rmtree(project_path) + + def test_cartesian_product_one(self): + parameters_content_list = [ + [ + {"a": 1}, + {"a": 2} + ] + ] + product_list = utils.gen_cartesian_product(*parameters_content_list) + self.assertEqual( + product_list, + [ + {"a": 1}, + {"a": 2} + ] + ) + + def test_cartesian_product_multiple(self): + parameters_content_list = [ + [ + {"a": 1}, + {"a": 2} + ], + [ + {"x": 111, "y": 112}, + {"x": 121, "y": 122} + ] + ] + product_list = utils.gen_cartesian_product(*parameters_content_list) + self.assertEqual( + product_list, + [ + {'a': 1, 'x': 111, 'y': 112}, + {'a': 1, 'x': 121, 'y': 122}, + {'a': 2, 'x': 111, 'y': 112}, + {'a': 2, 'x': 121, 'y': 122} + ] + ) + + def test_cartesian_product_empty(self): + parameters_content_list = [] + product_list = utils.gen_cartesian_product(*parameters_content_list) + self.assertEqual(product_list, [])