From 67725d58601423d1420ff311ee0ac441fd57a4ec Mon Sep 17 00:00:00 2001 From: debugtalk Date: Thu, 5 Dec 2019 18:44:38 +0800 Subject: [PATCH] refactor: move validate testcase code from validator to loader --- httprunner/cli.py | 2 +- httprunner/loader.py | 173 +++++++++++++++++++++++++++++++++++++++- httprunner/validator.py | 51 ------------ tests/test_loader.py | 70 ++++++++++++++++ tests/test_validator.py | 70 ---------------- 5 files changed, 242 insertions(+), 124 deletions(-) diff --git a/httprunner/cli.py b/httprunner/cli.py index 389fdc80..19a163f5 100644 --- a/httprunner/cli.py +++ b/httprunner/cli.py @@ -5,11 +5,11 @@ import sys from httprunner import __description__, __version__ from httprunner.api import HttpRunner from httprunner.compat import is_py2 +from httprunner.loader import validate_json_file from httprunner.logger import color_print from httprunner.report import gen_html_report from httprunner.utils import (create_scaffold, get_python2_retire_msg, prettify_json_file) -from httprunner.validator import validate_json_file def main(): diff --git a/httprunner/loader.py b/httprunner/loader.py index 191011e5..428d318a 100644 --- a/httprunner/loader.py +++ b/httprunner/loader.py @@ -4,6 +4,7 @@ import io import json import os import sys +import types import yaml @@ -17,6 +18,149 @@ except AttributeError: pass +# TODO: validate data format with JSON schema + +def is_testcase(data_structure): + """ check if data_structure is a testcase. + + Args: + data_structure (dict): testcase should always be in the following data structure: + + { + "config": { + "name": "desc1", + "variables": [], # optional + "request": {} # optional + }, + "teststeps": [ + test_dict1, + { # test_dict2 + 'name': 'test step desc2', + 'variables': [], # optional + 'extract': [], # optional + 'validate': [], + 'request': {}, + 'function_meta': {} + } + ] + } + + Returns: + bool: True if data_structure is valid testcase, otherwise False. + + """ + # TODO: replace with JSON schema validation + if not isinstance(data_structure, dict): + return False + + if "teststeps" not in data_structure: + return False + + if not isinstance(data_structure["teststeps"], list): + return False + + return True + + +def is_testcases(data_structure): + """ check if data_structure is testcase or testcases list. + + Args: + data_structure (dict): testcase(s) should always be in the following data structure: + { + "project_mapping": { + "PWD": "XXXXX", + "functions": {}, + "env": {} + }, + "testcases": [ + { # testcase data structure + "config": { + "name": "desc1", + "path": "testcase1_path", + "variables": [], # optional + }, + "teststeps": [ + # test data structure + { + 'name': 'test step desc1', + 'variables': [], # optional + 'extract': [], # optional + 'validate': [], + 'request': {} + }, + test_dict_2 # another test dict + ] + }, + testcase_dict_2 # another testcase dict + ] + } + + Returns: + bool: True if data_structure is valid testcase(s), otherwise False. + + """ + if not isinstance(data_structure, dict): + return False + + if "testcases" not in data_structure: + return False + + testcases = data_structure["testcases"] + if not isinstance(testcases, list): + return False + + for item in testcases: + if not is_testcase(item): + return False + + return True + + +def is_testcase_path(path): + """ check if path is testcase path or path list. + + Args: + path (str/list): file path or file path list. + + Returns: + bool: True if path is valid file path or path list, otherwise False. + + """ + if not isinstance(path, (str, list)): + return False + + if isinstance(path, list): + for p in path: + if not is_testcase_path(p): + return False + + if isinstance(path, str): + if not os.path.exists(path): + return False + + return True + + +def validate_json_file(file_list): + """ validate JSON testcase format + """ + for json_file in set(file_list): + if not json_file.endswith(".json"): + logger.log_warning("Only JSON file format can be validated, skip: {}".format(json_file)) + continue + + logger.color_print("Start to validate JSON file: {}".format(json_file), "GREEN") + + with io.open(json_file) as stream: + try: + json.load(stream) + except ValueError as e: + raise SystemExit(e) + + print("OK") + + ############################################################################### # file loader ############################################################################### @@ -244,6 +388,31 @@ def locate_file(start_path, file_name): ############################################################################### +def is_function(item): + """ Takes item object, returns True if it is a function. + """ + return isinstance(item, types.FunctionType) + + +def is_variable(tup): + """ Takes (name, object) tuple, returns True if it is a variable. + """ + name, item = tup + if callable(item): + # function or class + return False + + if isinstance(item, types.ModuleType): + # imported module + return False + + if name.startswith("_"): + # private property + return False + + return True + + def load_module_functions(module): """ load python module functions. @@ -262,7 +431,7 @@ def load_module_functions(module): module_functions = {} for name, item in vars(module).items(): - if validator.is_function(item): + if is_function(item): module_functions[name] = item return module_functions @@ -724,7 +893,7 @@ def load_api_folder(api_folder_path): 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") + or api_dict.get("name") if key != "api" or not api_id: raise exceptions.ParamsError( "Invalid API defined in {}".format(api_file_path)) diff --git a/httprunner/validator.py b/httprunner/validator.py index 17e71601..153eeb61 100644 --- a/httprunner/validator.py +++ b/httprunner/validator.py @@ -1,8 +1,6 @@ # encoding: utf-8 import collections -import io import json -import types from httprunner import exceptions, logger, parser @@ -171,55 +169,6 @@ def extend_validators(raw_validators, override_validators): return list(def_validators_mapping.values()) -############################################################################### -## validate varibles and functions -############################################################################### - - -def is_function(item): - """ Takes item object, returns True if it is a function. - """ - return isinstance(item, types.FunctionType) - - -def is_variable(tup): - """ Takes (name, object) tuple, returns True if it is a variable. - """ - name, item = tup - if callable(item): - # function or class - return False - - if isinstance(item, types.ModuleType): - # imported module - return False - - if name.startswith("_"): - # private property - return False - - return True - - -def validate_json_file(file_list): - """ validate JSON testcase format - """ - for json_file in set(file_list): - if not json_file.endswith(".json"): - logger.log_warning("Only JSON file format can be validated, skip: {}".format(json_file)) - continue - - logger.color_print("Start to validate JSON file: {}".format(json_file), "GREEN") - - with io.open(json_file) as stream: - try: - json.load(stream) - except ValueError as e: - raise SystemExit(e) - - print("OK") - - class Validator(object): """Validate tests diff --git a/tests/test_loader.py b/tests/test_loader.py index 21268074..6da7b278 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -476,3 +476,73 @@ class TestSuiteLoader(unittest.TestCase): api_file_path = os.path.join(os.getcwd(), "tests", "api", "get_token.yml") self.assertIn(api_file_path, self.tests_def_mapping["api"]) self.assertEqual(self.project_mapping["env"]["PROJECT_KEY"], "ABCDEFGH") + + def test_is_function(self): + func = lambda x: x + 1 + self.assertTrue(loader.is_function(func)) + self.assertTrue(loader.is_function(loader.is_testcase)) + + def test_is_testcases(self): + data_structure = "path/to/file" + self.assertFalse(loader.is_testcases(data_structure)) + data_structure = ["path/to/file1", "path/to/file2"] + self.assertFalse(loader.is_testcases(data_structure)) + + data_structure = { + "project_mapping": { + "PWD": "XXXXX", + "functions": {}, + "env": {} + }, + "testcases": [ + { # testcase data structure + "config": { + "name": "desc1", + "path": "testcase1_path", + "variables": [], # optional + }, + "teststeps": [ + # test data structure + { + 'name': 'test step desc1', + 'variables': [], # optional + 'extract': [], # optional + 'validate': [], + 'request': {} + }, + # test_dict2 # another test dict + ] + }, + # testcase_dict_2 # another testcase dict + ] + } + self.assertTrue(loader.is_testcases(data_structure)) + data_structure = [ + { + "name": "desc1", + "config": {}, + "api": {}, + "testcases": ["testcase11", "testcase12"] + }, + { + "name": "desc2", + "config": {}, + "api": {}, + "testcases": ["testcase21", "testcase22"] + } + ] + self.assertTrue(data_structure) + + def test_is_variable(self): + var1 = 123 + var2 = "abc" + self.assertTrue(loader.is_variable(("var1", var1))) + self.assertTrue(loader.is_variable(("var2", var2))) + + __var = 123 + self.assertFalse(loader.is_variable(("__var", __var))) + + func = lambda x: x + 1 + self.assertFalse(loader.is_variable(("func", func))) + + self.assertFalse(loader.is_variable(("unittest", unittest))) diff --git a/tests/test_validator.py b/tests/test_validator.py index cdaf9563..312a9e74 100644 --- a/tests/test_validator.py +++ b/tests/test_validator.py @@ -5,76 +5,6 @@ from httprunner import validator class TestValidator(unittest.TestCase): - def test_is_testcases(self): - data_structure = "path/to/file" - self.assertFalse(validator.is_testcases(data_structure)) - data_structure = ["path/to/file1", "path/to/file2"] - self.assertFalse(validator.is_testcases(data_structure)) - - data_structure = { - "project_mapping": { - "PWD": "XXXXX", - "functions": {}, - "env": {} - }, - "testcases": [ - { # testcase data structure - "config": { - "name": "desc1", - "path": "testcase1_path", - "variables": [], # optional - }, - "teststeps": [ - # test data structure - { - 'name': 'test step desc1', - 'variables': [], # optional - 'extract': [], # optional - 'validate': [], - 'request': {} - }, - # test_dict2 # another test dict - ] - }, - # testcase_dict_2 # another testcase dict - ] - } - self.assertTrue(validator.is_testcases(data_structure)) - data_structure = [ - { - "name": "desc1", - "config": {}, - "api": {}, - "testcases": ["testcase11", "testcase12"] - }, - { - "name": "desc2", - "config": {}, - "api": {}, - "testcases": ["testcase21", "testcase22"] - } - ] - self.assertTrue(data_structure) - - def test_is_variable(self): - var1 = 123 - var2 = "abc" - self.assertTrue(validator.is_variable(("var1", var1))) - self.assertTrue(validator.is_variable(("var2", var2))) - - __var = 123 - self.assertFalse(validator.is_variable(("__var", __var))) - - func = lambda x: x + 1 - self.assertFalse(validator.is_variable(("func", func))) - - self.assertFalse(validator.is_variable(("unittest", unittest))) - - def test_is_function(self): - func = lambda x: x + 1 - self.assertTrue(validator.is_function(func)) - self.assertTrue(validator.is_function(validator.is_testcase)) - def test_get_uniform_comparator(self): self.assertEqual(validator.get_uniform_comparator("eq"), "equals") self.assertEqual(validator.get_uniform_comparator("=="), "equals")