import unittest from Business.Class.HTMLTestRunner import HTMLTestRunner from Business.Class.ExcelUtils import * from Business.Class.JsonOrYaml import * from Base.Class.Http import * from Base.Class.Time import * from Base.Class.Open import * from Base.Class.Database import * from Base.Class.Encrypt import * from Base.Class.Logger import * def reparse(regexp, string): import re mut = isinstance(regexp, tuple) reg = [str(regexp), str(regexp[0])][mut] flg = (mut and eval('re.%s' % str(regexp[1]).upper())) or 0 if reg in ['', 'trim']: return ReText(string.strip()) else: return ReList(re.findall(reg, string, flg)) class ReText(str): def __getitem__(self, item): if isinstance(item, (int, bool)): return __class__(super().__getitem__(item)) else: return reparse(item, str(self.__str__())) class ReList(list): def __getitem__(self, item): if isinstance(item, (int, bool)): try: r = super().__getitem__(item) if isinstance(r, (list, tuple, set)): return __class__(r) elif isinstance(r, (dict,)): return ReDict(r) else: return ReText(r) except Exception: return __class__() else: try: return reparse(item, str(self.__str__())) except Exception: return ReText('') def __str__(self): try: return ReText(self[0]) except Exception: return ReText('') class ReDict(dict): def __getattr__(self, item): return self.__getitem__(item) def __getitem__(self, item): if item in self: r = super().__getitem__(item) if isinstance(r, (dict,)): return __class__(r) elif isinstance(r, (list, tuple, set)): return ReList(r) else: return ReText(r) else: try: return reparse(item, str(self.__str__())) except Exception: return ReText('') def __str__(self): try: if '_' in self: return ReText(self.__getitem__('_')) else: return json_encode(self, indent=None, unicode=False) except Exception: return ReText('') def update(self, *args, **kwargs): super().update(*args, **kwargs) return self class CustomAssert: @staticmethod def assertPreWith(string: str, sub: str): if not string.startswith(sub): raise AssertionError('%s does not starts with %s' % (string, sub)) @staticmethod def assertSufWith(string: str, sub: str): if not string.endswith(sub): raise AssertionError('%s does not ends with %s' % (string, sub)) class TestCase: _g = {} _l = {} def __init__(self, workbook: str, testcase: str, module: str = None, caseid: str = None, testlves=None, organize_mode=None, write_test_result=None): if isinstance(testlves, str): testlves = [value.strip() for value in list(filter(lambda x: x, testlves.split(',')))] tabs = { "HTTPConf": { "fixed": {}, "views": "A4:H*", "field": [ "Name", "Scheme", "Host", "DefaultHeader", "Timeout", "Session", "Proxies", "IgnoreSSLCertError" ] }, "DataBase": { "fixed": {}, "views": "J4:P*", "field": [ "Name", "Host", "Port", "User", "Password", "Database", "Charset" ] }, "TestCase": { "fixed": {}, "views": "A7:AH*", "field": [ "Flag", "PreExecRule", "PreExecCase", "CaseModule", "CaseId", "CaseTitle", "CaseDesc", "CaseLevel", "HTTPChannel", "HTTPMethod", "HTTPUri", "HTTPQuery", "HTTPRedirect", "HTTPHeader", "HTTPCookie", "HTTPParamType", "HTTPParamContent", "HTTPParamOfFile", "HTTPExtract", "HTTPAssertStatus", "HTTPAssertReason", "HTTPAssertHeader", "HTTPAssertCookie", "HTTPAssertBody", "HTTPAssertJson", "BaseChannel", "BaseSql", "BaseExtract", "BaseAssertData", "BaseAssertRows", "DataFileSet", "TestAuthor", "TestTime", "TestResult" ] } } import os from pandas.core.frame import DataFrame self.init = Excel().open(workbook) data = {} for k, v in tabs.items(): match k: case 'TestCase': sheet = testcase case 'DataBase': sheet = '请求配置' case 'HTTPConf': sheet = '请求配置' case _: continue self.init.select(sheet) data[k] = {} if 'fixed' in v.keys(): data[k]['fixed'] = {} for key, value in v['fixed'].items(): data[k]['fixed'][key] = self.init.cellGet(cell=value) if 'views' in v.keys() and 'field' in v.keys(): data[k]['views'] = read_view_dict( filename=workbook, sheet=sheet, area=v['views'], fields=v['field'], auto_truncate=True) data[k]['ocell'] = read_view_dict( filename=workbook, sheet=sheet, area=v['views'], fields=v['field'], auto_truncate=True, object_cell=True) self.module_name = module self.testcase_id = caseid self.write_test_result = write_test_result self.case = '%s_%s' % (testcase, module) if module else testcase self.dirs = os.path.dirname(workbook) self.request = Request() self.session = Session() self.h_vc = DataFrame(data['HTTPConf']['views']['data']).reset_index(drop=False, inplace=False) self.b_vc = DataFrame(data['DataBase']['views']['data']).reset_index(drop=False, inplace=False) self.c_vc = DataFrame(data['TestCase']['views']['data']).reset_index(drop=False, inplace=False) self.h_oc = DataFrame(data['HTTPConf']['ocell']['data']).reset_index(drop=False, inplace=False) self.b_oc = DataFrame(data['DataBase']['ocell']['data']).reset_index(drop=False, inplace=False) self.c_oc = DataFrame(data['TestCase']['ocell']['data']).reset_index(drop=False, inplace=False) self.h_excel_object = data['HTTPConf']['ocell']['excel_object'] self.b_excel_object = data['DataBase']['ocell']['excel_object'] self.c_excel_object = data['TestCase']['ocell']['excel_object'] self._set_levels(testlves) self.assert_unit = unittest.TestCase() self.assert_cust = CustomAssert() self.assert_list = [ {'sign': ' == ', 'method': self.assert_unit.assertEqual, 'reverse': 0, 'cast': 0}, {'sign': ' != ', 'method': self.assert_unit.assertNotEqual, 'reverse': 0, 'cast': 0}, {'sign': ' >= ', 'method': self.assert_unit.assertGreaterEqual, 'reverse': 0, 'cast': float}, {'sign': ' <= ', 'method': self.assert_unit.assertLessEqual, 'reverse': 0, 'cast': float}, {'sign': ' =~ ', 'method': self.assert_unit.assertIn, 'reverse': 1, 'cast': str}, {'sign': ' !~ ', 'method': self.assert_unit.assertNotIn, 'reverse': 1, 'cast': str}, {'sign': ' > ', 'method': self.assert_unit.assertGreater, 'reverse': 0, 'cast': float}, {'sign': ' < ', 'method': self.assert_unit.assertLess, 'reverse': 0, 'cast': float}, {'sign': ' not in ', 'method': self.assert_unit.assertNotIn, 'reverse': 0, 'cast': 0}, {'sign': ' in ', 'method': self.assert_unit.assertIn, 'reverse': 0, 'cast': 0}, {'sign': ' *= ', 'method': self.assert_cust.assertPreWith, 'reverse': 0, 'cast': str}, {'sign': ' =* ', 'method': self.assert_cust.assertSufWith, 'reverse': 0, 'cast': str}, ] self.pre_executed_list = [] self.__realnb__ = 0 self.__currnb__ = 0 match str(organize_mode).upper(): case 'BY_ORDER' | '1' | '10': self.organize_mode = 10 case 'BY_LEVEL' | '2' | '20': self.organize_mode = 20 case _: self.organize_mode = 10 def _set_levels(self, level_list=None): if isinstance(level_list, (list, tuple, str)): self.leve_list = level_list else: self.leve_list = None @staticmethod def _match_case_type(value=None): match value: case '正常用例': return 10 case '前置用例': return 20 case '关闭用例' | _: return 50 @staticmethod def _match_prex_rule(value=None): match value: case '全局一次': return 10 case '总是执行' | _: return 50 @staticmethod def _match_bool(value=None): return value in ['是', '开启', '打开', 'True', 'true', 'TRUE', True, 'Yes', 'yes', 'YES', 'Y', 'y', '1', 1, 1.0] def _match_leve_run(self, value=None): if self.leve_list is None: return True return value in self.leve_list def _merge_dict(self, dict_ori, dict_add): if isinstance(dict_ori, dict) and isinstance(dict_add, dict): for k, v in dict_ori.items(): if k in dict_add.keys(): if isinstance(v, dict): dict_ori[k] = self._merge_dict(dict_ori[k], dict_add[k]) else: dict_ori[k] = dict_add[k] for k, v in dict_add.items(): if k not in dict_ori.keys(): dict_ori[k] = dict_add[k] return dict_ori @staticmethod def _parse_formula(text, operator): locals().__setitem__('quota', 0) none_quota = [] none_range = [] for i in range(len(text)): this = text[i] match this: case '\'': match locals().get('quota'): case 0: locals().__setitem__('quota', 1) case 1: locals().__setitem__('quota', 0) case '\"': match locals().get('quota'): case 0: locals().__setitem__('quota', 2) case 2: locals().__setitem__('quota', 0) case _: match locals().get('quota'): case 0: none_quota.append(i) for i in range(len(none_quota) + 1): last = (i > 0 and none_quota[i - 1]) or -1 this = (i < len(none_quota) and none_quota[i]) or 9999 if (this - last) > 1: none_range.append(last) none_range.append(this) none_range.pop(0) none_range.pop(-1) none_range_list = [] for i in range(int(len(none_range) / 2)): none_range_list.append([none_range[i * 2], none_range[i * 2 + 1]]) for v in none_range_list: p = text.find(operator, v[0], v[1] + 1) if not p == -1: return [text[:p], text[p + len(operator):]] else: return False @staticmethod def _sub_encr(text, encr_dict): if not isinstance(text, str): return text for encr_text in re.findall('([0-9a-zA-Z_]+)\((.*?)\)', text): if encr_text[0] not in encr_dict.keys(): continue try: value = encr_dict[encr_text[0]](encr_text[1]) except Exception: value = '' if value is None: value = '' text = text.replace('%s(%s)' % (encr_text[0], encr_text[1]), str(value)) return text def _sub_vars(self, text, vars_dict): if not isinstance(text, str): return text for variable_name in re.findall('\${(.*?)}', text): try: value = vars_dict[variable_name] except Exception: value = '' if self._is_nan(value) or value is None: value = '' text = text.replace('${%s}' % variable_name, str(value)) return text def _sub_alls(self, text): vars_dict = [self._g, self._l] if isinstance(vars_dict, list): d = {} for v in vars_dict: d.update(v) vars_dict = d encr_dict = { 'base64': base64_encode, 'base64_encode': base64_encode, 'base64_decode': base64_decode, 'md5': md5, 'sha1': sha1, 'sha224': sha224, 'sha256': sha256, 'sha384': sha384, 'sha512': sha512, 'urlencode': urlencode, 'urldecode': urldecode, } text = self._sub_vars(text=text, vars_dict=vars_dict) text = self._sub_encr(text=text, encr_dict=encr_dict) return text def _sub_variable_auto(self, data): if isinstance(data, list): for i in range(len(data)): if not isinstance(data[i], str): continue data[i] = self._sub_alls(text=data[i]) return data if isinstance(data, dict): for k in data.keys(): if not isinstance(data[k], str): continue data[k] = self._sub_alls(text=data[k]) return data if isinstance(data, str): return self._sub_alls(data) return data @staticmethod def _to_string(value): import math if value is None: return '' if isinstance(value, (float, int)) and str(value) == str(float('NaN')): return '' if isinstance(value, (float,)) and math.modf(value)[0] == 0: return str(int(value)) return str(value) @staticmethod def _to_number(value): if not value: return 0 if value is True: return 1 if isinstance(value, (float, int)) and str(value) == str(float('NaN')): return 0 return int(float(value)) @staticmethod def _is_nan(value): if isinstance(value, (float, int)) and str(value) == str(float('NaN')): return True else: return False def _extract_variable(self, extract, vars_dict: dict, domain: str | list | dict): if not extract: return None if not isinstance(extract, (list, dict)): extract = auto_decode(extract) if isinstance(domain, str): match domain: case 'locals': domain = self._l case 'global' | _: domain = self._g vars_dict = {k if not isinstance(k, str) else k.lower(): v for k, v in vars_dict.items()} import re for k, v in (extract or {}).items(): d = re.findall('^([0-9A-Za-z_]+).*?', v)[0] locals().__setitem__('r', vars_dict[d.lower()]) domain[str(k)] = str(eval(v.replace(d, 'r', 1))) return True def _assert(self, assert_items, assert_lists): for assert_item in assert_items: expect = self._to_string(assert_item['expect']) if not expect: continue actual = assert_item['actual'] if isinstance(actual, (int, bool, str)): actual = ReText(actual) if isinstance(actual, (tuple, list, set)): actual = ReList(actual) if isinstance(actual, (dict,)): actual = ReDict(actual) assert_rows = 0 assert_real = 0 for name, value in { json_encode(None): None, json_encode(bool(0)): bool(0), json_encode(bool(1)): bool(1) }.items(): locals().__setitem__(name, value) for line in list(filter(lambda x: x, expect.split("\n"))): assert_rows += 1 for oper in assert_lists: form = self._parse_formula(re.compile('(\$)(?!{).*?').sub('actual', line), oper['sign']) if not form: continue for i in range(len(form)): form[i] = self._sub_variable_auto(form[i]).replace("\n", "\\n") assert_real += 1 oper['reverse'] and form.reverse() assert_meth = oper['method'] assert_parm = [eval(form[0]), eval(form[1])] for i in range(len(assert_parm)): try: if assert_parm[i]['_']: assert_parm[i] = str(assert_parm[i]) except TypeError as e: pass cast = oper['cast'] if cast: assert_parm[i] = cast(assert_parm[i]) assert_meth(assert_parm[0], assert_parm[1]) break if 1 <= assert_real != assert_rows: raise Exception('wrong assertion statement') if 0 == assert_real: expect = self._sub_variable_auto(expect) actual = str(actual) assert actual == expect, '%s != %s' % (actual, expect) def _test_unit(self, func_name, data, main=None): log.d(func_name) if data['HTTPUri']: if not data['HTTPChannel']: raise Exception('channel not set') from pandas.core.frame import DataFrame http = self.h_vc.where((self.h_vc['Name'] == data['HTTPChannel']), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict() res_kwargs = { 'method': data['HTTPMethod'], 'url': self._sub_variable_auto((http['Scheme'] or 'http').lower() + '://' + (http['Host'] or '') + data['HTTPUri']), 'query': self._sub_variable_auto(auto_decode(data['HTTPQuery'])), 'data': None, 'json': None, 'file': None, 'header': {}, 'cookie': self._sub_variable_auto(auto_decode(data['HTTPCookie'])), 'auth': None, 'timeout': self._to_number(http['Timeout']) or 15, 'proxy': self._to_string(http['Proxies']), 'auto_redirect': self._match_bool(data['HTTPRedirect']), 'ignore_cert_error': self._match_bool(http['IgnoreSSLCertError']), 'debug': True } res_kwargs['header'].update((auto_decode(http['DefaultHeader']) or {})) res_kwargs['header'].update((self._sub_variable_auto(auto_decode(data['HTTPHeader'])) or {})) match str(data['HTTPParamType']).strip().title(): case 'Json': res_kwargs['json'] = self._sub_variable_auto(auto_decode(data['HTTPParamContent'])) case 'Form': res_kwargs['data'] = self._sub_variable_auto(auto_decode(data['HTTPParamContent'])) case _: res_kwargs['data'] = self._sub_variable_auto(data['HTTPParamContent']) res_kwargs['file'] = locals().setdefault('f', self._sub_variable_auto(auto_decode( data['HTTPParamOfFile']))) and {k: open(os.path.abspath(os.path.join(self.dirs, './%s' % v)), 'rb') for k, v in locals().get('f').items()} res = [self.request, self.session][self._match_bool(http['Session'])].http(**res_kwargs) log.d("\n" + res['brief']) hvar = { 'Status': ReText(res['status'] or ''), 'Reason': ReText(res['reason'] or ''), 'Header': ReDict(res['header'] or {}).update({'_': "\n".join([k + ':' + "\x20" + (v or '') for k, v in dict(res['header'] or {}).items()])}), 'Cookie': ReDict(res['cookie'] or {}).update({'_': "; ".join([k + '=' + (v or '') for k, v in dict(res['cookie'] or {}).items()])}), 'Body': ReText(res['text'] or ''), 'Json': ReDict(res['json'] or {}), } self._extract_variable(extract=data['HTTPExtract'], vars_dict=hvar, domain='global') self._assert([ {'expect': data['HTTPAssertStatus'], 'actual': hvar['Status']}, {'expect': data['HTTPAssertReason'], 'actual': hvar['Reason']}, {'expect': data['HTTPAssertHeader'], 'actual': hvar['Header']}, {'expect': data['HTTPAssertCookie'], 'actual': hvar['Cookie']}, {'expect': data['HTTPAssertBody'], 'actual': hvar['Body']}, {'expect': data['HTTPAssertJson'], 'actual': hvar['Json']}, ], self.assert_list) if data['BaseSql']: if not data['BaseChannel']: raise Exception('channel not set') base = self.b_vc.where((self.b_vc['Name'] == data['BaseChannel']), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict() conn = MySQL().connect( host=base['Host'], port=int(base['Port']), user=base['User'], password=base['Password'], database=base['Database'], charset=base['Charset'], cursor='Dict' ) conn.execute(data['BaseSql']) bvar = { 'Data': ReList(conn.fetchall() or []), 'Rows': conn.count() or 0 } log.d("\n" + conn.brief()) self._extract_variable(extract=data['BaseExtract'], vars_dict=bvar, domain='global') self._assert([ {'expect': data['BaseAssertData'], 'actual': bvar['Data']}, {'expect': data['BaseAssertRows'], 'actual': bvar['Rows']}, ], self.assert_list) def test(self, index, update_global=None, update_locals=None, func_name=''): if isinstance(update_global, (dict,)): self._g.update(update_global) if isinstance(update_locals, (dict,)): self._l.update(update_locals) def write_test_result(object_cell, test_result): for cell, new_cell_value in [[object_cell['TestResult'], test_result], [object_cell['TestTime'], time_strftime(fmt='%Y-%m-%d %H:%M:%S')]]: cell.value = new_cell_value self.__currnb__ += 1 if self.write_test_result and self.__currnb__ >= self.__realnb__: self.c_excel_object.save() main_case_object_cell = self.c_oc.loc[index].to_dict() try: main_case = dict(self.c_vc.loc[index].to_dict()) prex_list = list(filter(lambda x: x, [value.strip() for value in (main_case['PreExecCase'] or '').replace(',', "\n").split("\n")])) for case_id in prex_list: if self._match_prex_rule(main_case['PreExecRule']) == 10 and case_id in self.pre_executed_list: continue data = self.c_vc.where((self.c_vc['CaseId'] == case_id), inplace=False).dropna(how='all').reset_index(drop=True, inplace=False).loc[0].to_dict() if not self._match_case_type(data['Flag']) == 20: raise Exception('A non-Pre-exec case was called.') if data['PreExecCase'] or data['PreExecRule']: raise Exception('Pre-exec case cannot be nested.') if data['DataFileSet']: raise Exception('Pre-exec case cannot use data files set.') self.pre_executed_list.append(case_id) self._test_unit(func_name=func_name, data=data) # self.__getattribute__(func_name)(data=data) self._test_unit(func_name=func_name, data=main_case, main=True) # self.__getattribute__(func_name)(data=main_case, main=True) self._l and self._l.clear() write_test_result(main_case_object_cell, 'Passed') except AssertionError: self._l and self._l.clear() write_test_result(main_case_object_cell, 'Failed') raise except Exception: self._l and self._l.clear() write_test_result(main_case_object_cell, 'Errors') raise def main(self): class ClassTestCase(unittest.TestCase): pass ClassTestCase.__qualname__ = self.case ClassTestCase._test_ = self.test from pandas import read_csv while 1: if self.organize_mode == 20: levels_list = self.leve_list loop = len(levels_list) else: levels_list = None loop = 1 break serial = 0 realnb = 0 for n in range(loop): filter_module = self.c_vc['CaseModule'] == self.module_name if self.module_name else self.c_vc['CaseModule'] != '*' filter_caseid = self.c_vc['CaseId'] == self.testcase_id if self.testcase_id else self.c_vc['CaseId'] != '*' if self.organize_mode == 20: case = self.c_vc.where(filter_module & filter_caseid & (self.c_vc['CaseLevel'] == levels_list[n]), inplace=False).dropna(how='all') else: case = self.c_vc.where(filter_module & filter_caseid, inplace=False).dropna(how='all') for i in range(len(case)): data = case.iloc[i].to_dict() if not (self._match_case_type(data['Flag']) == 10 and self._match_leve_run(data['CaseLevel'])): continue serial += 1 filename = data['DataFileSet'] or '' if filename: data_file = read_csv(open_auto(os.path.abspath(os.path.join(self.dirs, './%s' % filename))), keep_default_na=False) data_file_rows = len(data_file) data_file_flag = 1 else: data_file = None data_file_rows = 0 data_file_flag = 0 j = 0 while j < data_file_rows or data_file_flag == 0: match data_file_flag: case 1: vars_data = data_file.loc[j].to_dict() func_docs = str(data['CaseLevel'] or 'P?') + ':' + str(data['CaseTitle'] or '') + '_' + '_'.join([str(v) for k, v in vars_data.items()])[:32] func_name = 'test%s_%s_%s' % (str('%04d' % serial), str(data['CaseId']), str('%04d' % (j + 1))) once_do_break = 0 case _: vars_data = None func_docs = str(data['CaseLevel'] or 'P?') + ':' + str(data['CaseTitle'] or '') func_name = 'test%s_%s' % (str('%04d' % serial), str(data['CaseId'])) once_do_break = 1 def func(self, index=data['index'], update_global=None, update_locals=vars_data, func_name=func_name): return self._test_(index=index, update_global=update_global, update_locals=update_locals, func_name=func_name) func.__doc__ = func_docs type.__setattr__(ClassTestCase, func_name, func) realnb += 1 j += 1 if once_do_break: break self.__realnb__ = realnb self.__currnb__ = 0 return ClassTestCase if __name__ == '__main__': def read_runner_config(file: str): import os path = os.path.abspath(os.path.join(os.getcwd(), file)) return auto_decode(open(path, 'r', encoding='utf-8').read()) def abs_path(file: str): if isinstance(file, str): return os.path.abspath(os.path.join(os.getcwd(), file)) else: return None from argparse import ArgumentParser argparser = ArgumentParser( argument_default='', description='API Test Runner', usage="\n" + os.path.basename(__file__) + ' [-c CONFIG]' + "\n" + os.path.basename(__file__) + ' [-w WORKBOOK] [-t TESTCASE] [-m MODULE] [-a CASEID] [-l LEVELS] [-o ORGANIZE] [-r WRITE_TEST_RESULT] [-v VERBOSITY] [-p REPORT] [-x LOG] [-i TITLE] [-d DESCRIPTION]' ) argparser.add_argument( '-c', required=bool(0), help='Load runner configuration file.', choices=None, default=None, dest='config' ) argparser.add_argument( '-w', required=bool(0), help='Excel workbook path.', choices=None, default='./', dest='workbook' ) argparser.add_argument( '-t', required=bool(0), help='Testcase worksheet name.', choices=None, default='测试用例', dest='testcase' ) argparser.add_argument( '-m', required=bool(0), help='You can specify a module name.', choices=None, default=None, dest='module' ) argparser.add_argument( '-a', required=bool(0), help='You can specify a testcase id.', choices=None, default=None, dest='caseid' ) argparser.add_argument( '-l', required=bool(0), help='Case levels can be specified, use comma to separate, e.g. P0,P1,P2.', choices=None, default=None, dest='levels' ) argparser.add_argument( '-o', required=bool(0), help='Case organization mode, 1: by order, 2: by level, default: 1.', choices=[1, 2], default=None, dest='organize', type=int ) argparser.add_argument( '-r', required=bool(0), help='Enable writing test results to current worksheet, default: 0.', choices=[0, 1], default=None, dest='write_test_result', type=int ) argparser.add_argument( '-v', required=bool(0), help='Level of report and log verbosity(0-5), default: 1.', choices=[0, 1, 2, 3, 4, 5], default=1, dest='verbosity', type=int ) argparser.add_argument( '-p', required=bool(0), help='Output path of report.', choices=None, default=None, dest='report' ) argparser.add_argument( '-x', required=bool(0), help='Output path of log.', choices=None, default=None, dest='log' ) argparser.add_argument( '-i', required=bool(0), help='Report title.', choices=None, default=None, dest='title' ) argparser.add_argument( '-d', required=bool(0), help='Report description.', choices=None, default=None, dest='description' ) args = argparser.parse_args() import sys if len(sys.argv) <= 1: argparser.print_help() exit(0) if args.config: config = read_runner_config(args.config) else: config = { 'verbosity': args.verbosity, 'report': args.report, 'log': args.log, 'title': args.title, 'description': args.description, 'suite': [{"workbook": args.workbook, "testcase": args.testcase, "module": args.module, "caseid": args.caseid, "levels": args.levels, "organize": args.organize, "result": args.write_test_result}] } testSuite = unittest.TestSuite() for obj in config['suite']: testSuite.addTest(unittest.makeSuite( testCaseClass=TestCase(workbook=obj['workbook'], testcase=obj['testcase'], module=obj['module'], caseid=obj['caseid'], testlves=obj['levels'], organize_mode=obj['organize'], write_test_result=obj['result']).main(), prefix='test' )) HTMLTestRunner(verbosity=config['verbosity'], report=abs_path(config['report'] or None), log=abs_path(config['log'] or None), title=config['title'] or None, description=config['description'] or None).run(testSuite)