def check_response_text(fields, response): error_info = "" log.debug( Template("fields: $fields, response: $response").substitute( fields=fields, response=response)) if not fields: return error_info if not isinstance(fields, str): raise TypeError("neet to check fields is not a str") for text in fields.split(','): text = text.lstrip("'").lstrip('"').rstrip("'").rstrip('"') log.debug(Template("待校验的内容: $text").substitute(text=text)) if isinstance(response, dict): # if not response.get(text): isempty = jmespath.search(text, response) if isempty is None: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=response) else: if text not in response: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=response) return error_info
def send_text(self, msg, ats=None): """ func: 发送钉钉群消息 :param msg: text message conntent :param ats: phone list of to @, element "all" equal to @all :return: """ timestamp, sign = self._secret() url_ = self.token url_ = url_ + "×tamp={}&sign={}".format(timestamp, sign) log.debug('dingrobot complete url is: {}'.format(url_)) if ats is None: ats = [] text_msg = {"msgtype": "text", "text": {"content": msg}} at_info = {"atMobiles": [], "isAtAll": False} for item in ats: if item == "all": at_info["isAtAll"] = True elif isinstance(item, int): at_info["atMobiles"].append(item) else: pass text_msg["at"] = at_info header = {"Content-Type": "application/json"} with requests.post(url_, json.dumps(text_msg), headers=header) as res: log.info('dingrobot response is: {}'.format(res.text))
def get(self): res = requests.get(url=self.url, params=self.params, headers=self.header) log.debug( string.Template("response json: $res").substitute(res=res.json())) return res
def parse_case(self): """ 对所有用例根据sheetname进行转换 :return: """ all_sheets = {} mapper = settings.excel_fields new_excel_file = os.path.join(settings.old_testcase_file_path, "new_{}".format(os.path.split(self.file_name)[-1])) excel_helper = CaseWrite(list(self.all_sheets.keys())) for sheet_name, sheet in self.all_sheets.items(): testcases = list(self._read_row(sheet)) log.debug(Template("testcase: $testcases").substitute(testcases=testcases)) testcases = self._format(testcases) all_sheets[sheet_name] = [] if testcases: self.sheet_case = testcases[1:] all_sheets[sheet_name].append(self.testcase_switch()) for case in self.sheet_case: data_list = [""] * 25 data_list[mapper['order']] = case['order_index'] if case.get('order_index') else "" data_list[mapper['casename']] = case['tc_name'] if case.get('tc_name') else "" data_list[mapper['description']] = case['description'] if case.get('description') else "" data_list[mapper['url']] = case['url'] if case.get('url') else "" data_list[mapper['method']] = case['method'] if case.get('method') else "" data_list[mapper['params']] = case['params'] if case.get('params') else "" data_list[mapper['header']] = case['header'] if case.get('header') else "" data_list[mapper['interface_var']] = case['interface_var'] if case.get('interface_var') else "" data_list[mapper['wait_time']] = case['wait_time'] if case.get('wait_time') else "" data_list[mapper['verify_fields']] = case['verify_fields'] if case.get('verify_fields') else "" data_list[mapper['res_text']] = case['res_text'] if case.get('res_text') else "" data_list[mapper['module']] = case['test_task_key'] if case.get('test_task_key') else "" excel_helper.append(sheet_name, data_list)
def _order_for_testcase(self, cases, base_num): """ func:用于给所有测试用例编排执行顺序 :param cases: :return: """ orders = self._get_cases_orders(cases) log.debug( string.Template("**************$o****************").substitute( o=orders)) max_num = max(orders) + 1 if orders else 1 ordered = [] for case in cases: try: order = int(case['order']) if case['order'] else None except Exception as e: log.error( string.Template( "integer order number error, error info: $e"). substitute(e=e)) order = None if order: case['order'] = base_num + order else: case['order'] = base_num + max_num max_num += 1 ordered.append(case) return ordered
def parse_testsuite_fields_lines(self, content): """ func: 解析测试sheet中局部变量、初始化数据、接口测试、数据恢复所在的行位置 :param content: sheet所有行的数据 :return: 局部变量、初始化数据、接口测试、数据恢复所在的行位置 """ lines_info = {} lines_info_result = {} if not isinstance(content, list): raise TypeError("params is not a list object") print(content) for num in range(0, len(content)): if '局部变量' == content[num][0].value: lines_info[self.testsuite_local_vars_line_name] = num if '初始化数据' == content[num][0].value: lines_info[self.testsuite_init_line_name] = num elif '接口测试' == content[num][0].value: lines_info[self.testsuites_testcase_line_name] = num elif '数据恢复' == content[num][0].value: lines_info[self.testsuites_restore_line_name] = num log.info( string.Template("line info: $lines_info").substitute( lines_info=lines_info)) lines_info_order = sorted(lines_info.items(), key=lambda x: x[1], reverse=False) fields_num = len(lines_info_order) if fields_num == 4: lines_info_result[lines_info_order[0][0]] = ( lines_info_order[0][1], lines_info_order[1][1]) lines_info_result[lines_info_order[1][0]] = ( lines_info_order[1][1], lines_info_order[2][1]) lines_info_result[lines_info_order[2][0]] = ( lines_info_order[2][1], lines_info_order[3][1]) lines_info_result[lines_info_order[3][0]] = ( lines_info_order[3][1], len(content)) if fields_num == 3: lines_info_result[lines_info_order[0][0]] = ( lines_info_order[0][1], lines_info_order[1][1]) lines_info_result[lines_info_order[1][0]] = ( lines_info_order[1][1], lines_info_order[2][1]) lines_info_result[lines_info_order[2][0]] = ( lines_info_order[2][1], len(content)) elif fields_num == 2: lines_info_result[lines_info_order[0][0]] = ( lines_info_order[0][1], lines_info_order[1][1]) lines_info_result[lines_info_order[1][0]] = ( lines_info_order[1][1], len(content)) elif fields_num == 1: lines_info_result[lines_info_order[0][0]] = ( lines_info_order[0][1], len(content)) log.debug( string.Template("lines_info_result: $lines_info_result"). substitute(lines_info_result=lines_info_result)) return lines_info_result
def __init__(self, api_info): self.api_info = api_info log.debug( string.Template("api_info: $api_info").substitute( api_info=self.api_info)) self.method = self.api_info['method'] self.url = self.api_info['url'] self.params = self.api_info['params'] if self.api_info[ 'params'] else None self.header = self.api_info['header'] if self.api_info[ 'header'] else None
def reg_compare(k, v, r): info = "" v = v.lstrip('<#reg>') res = jmespath.search(k, r) log.debug( Template("*************$res*************").substitute(res=res)) try: if not re.match(v, res): info += "响应结果使用正则表达式匹配失败,请检查," except Exception as e: info += Template("响应结果使用正则表达式匹配异常,异常信息:$e").substitute(e=e) return info
def query(self, sql): with self.__conn.cursor() as cursor: log.info('query sql is: {}'.format(sql)) try: cursor.execute(sql) # data_one = cursor.fetchone() data_all = cursor.fetchall() log.debug(Template('query results is: $all').substitute(all=data_all)) return data_all except Exception as e: log.error('query fail, error info: {}, the sql is: {}'.format(str(e), str(sql))) return False
def _secret(cls): timestamp = str(round(time.time() * 1000)) secret = settings.dingrobot_secret secret_enc = secret.encode('utf-8') string_to_sign = '{}\n{}'.format(timestamp, secret) string_to_sign_enc = string_to_sign.encode('utf-8') hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest() sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) log.debug('dingrobot secret timestamp is: {}, sign is: {}'.format( timestamp, sign)) return timestamp, sign
def _read_file(self): """ 读取Excel文件,解析基础配置sheet,数据初始化sheet, 数据恢复sheet, 公共方法sheet和所有测试套sheet :return: """ all_sheets = {} test_suite = [] try: workbook = xlrd.open_workbook(self.filepath) except Exception as e: log.exception( string.Template("open testcase file fail, please check! %e"). substitute(e=e)) return False sheet_names = workbook.sheet_names() log.debug( string.Template("testcase sheets: $sheet_names").substitute( sheet_names=sheet_names)) # print(sheet_names) if '基础配置' in sheet_names: base_configs = workbook.sheet_by_name('基础配置') all_sheets['base_configs'] = base_configs sheet_names.remove('基础配置') if '数据初始化' in sheet_names: initialize_datas = workbook.sheet_by_name('数据初始化') all_sheets['initialize_datas'] = initialize_datas sheet_names.remove('数据初始化') if '数据恢复' in sheet_names: restore = workbook.sheet_by_name('数据恢复') all_sheets['restore'] = restore sheet_names.remove('数据恢复') if '公共方法' in sheet_names: common_func = workbook.sheet_by_name('公共方法') all_sheets['common_func'] = common_func sheet_names.remove('公共方法') if '全局变量' in sheet_names: global_vars = workbook.sheet_by_name('全局变量') all_sheets['global_vars'] = global_vars sheet_names.remove('全局变量') for sheet in sheet_names: test_suite.append(workbook.sheet_by_name(sheet)) log.info( string.Template( "All the test suite that need to be tested: $test_suite"). substitute(test_suite=test_suite)) all_sheets['testsuites'] = test_suite return all_sheets
def check_response_text(fields, response): error_info = "" log.debug( Template("fields: $fields, response: $response").substitute( fields=fields, response=response)) if not fields: return error_info if isinstance(fields, str): log.debug('is a string fields, value is:'.format(fields)) fs = fields.split(',') elif isinstance(fields, list): log.debug('is a list fields, value is: '.format(str(fields))) fs = fields else: log.error('fields param type error') return 'check res_text error, fields param type error' for text in fs: text = text.lstrip("'").lstrip('"').rstrip("'").rstrip('"') if text: log.debug(Template("待校验的内容: $text").substitute(text=text)) if isinstance(response, dict): isempty = jmespath.search(text, response) if isempty is None: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=response) else: if text not in response: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=response) return error_info
def send(self, mail_body, receivers, attachs=None, subject="auto-test mail notify"): """ func: 发送邮件功能 :param mail_body: 邮件文本主体,字符串类型 :param receivers: 接收者列表,list类型 :param attachs: 需要添加的附件,list类型,list中的每个元素需要是dict类型,并且需要有filename和filepath两个属性 :param subject: 邮件主题 :return: """ if not attachs: if not isinstance(attachs, list): log.error('attach param is not a list') attach = None msg = MIMEMultipart() # 支持邮件中附带附件 msg['Subject'] = Header(subject, 'utf-8') msg['From'] = Header(self.sender, 'utf-8') for receiver in receivers: msg['To'] = Header(receiver, 'utf-8') mail_body = "(本邮件是程序自动下发的,请勿回复!)\r\n" + mail_body text_content = MIMEText(mail_body, 'plain', 'utf-8') msg.attach(text_content) # 添加附件 if attachs: attachs = self.attach_check(attachs) for att in attachs: filepath = att.get('filepath') filename = att.get('filename') log.debug('filepath is: {}'.format(filepath)) att_tmp = MIMEText( open(filepath, 'rb').read(), 'base64', 'utf-8') att_tmp['Content-type'] = 'application/octet-stream' att_tmp[ 'Content-Disposition'] = 'attachment; filename="{}"'.format( filename) msg.attach(att_tmp) try: if self.smtp_link(): self.smtp_handler.sendmail(self.sender, receivers, msg.as_string()) log.info("send mail success!") except Exception as e: log.error("发送邮件异常, 错误信息:{}".format(e))
def open_report_server(self, report_dir, ip=None, port=None): if not port: port = 55555 suffix_dir = os.path.split(report_dir)[0] relative_path = os.path.split(report_dir)[1] self.trend_detail(report_dir) os.chdir(suffix_dir) self.check_port_is_open(port) generate_cmd = "allure generate {} -o {} -c".format( relative_path, self.allure_report) if ip: open_server_cmd = "allure open {} -h {} -p {}".format( self.allure_report, ip, port) else: open_server_cmd = "allure open {} -p {}".format( self.allure_report, port) obj = subprocess.Popen(generate_cmd, shell=True) obj.communicate() obj = subprocess.Popen(open_server_cmd, shell=True, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE) couter = 0 for item in iter(obj.stdout.readline, 'b'): couter += 1 encode_type = chardet.detect(item) if encode_type['encoding'] == 'utf-8': item = item.decode('utf-8') elif encode_type['encoding'] == 'Windows-1252': item = item.decode('Window-1252') else: pass log.debug('subprocess out is: {}'.format(str(item))) pattern = re.compile(r'Server started at <(.+?)>\.') rs = re.search(pattern, str(item)) if rs: url = rs.group(1) log.info('reports server is: {}'.format(url)) return url time.sleep(0.5) if couter > 100: return "获取allure报告服务地址失败"
def _file_parse(self, file): with open(file, 'r', encoding='utf-8') as f: content = f.read() try: json_conten = json.loads(content) log.debug( Template("转换为json格式后的api信息:$api").substitute(api=json_conten)) log.debug(type(json_conten)) except Exception as e: print("调试文件格式不符合json格式,解析出错,请检查!错误信息: {}".format(str(e))) log.error( Template("调试文件格式不符合json格式,解析出错,请检查!错误信息: $e").substitute(e=e)) sys.exit(-1) if isinstance(json_conten, dict): if not json_conten.get("url") and not json_conten.get('method'): print("调试用例没有url信息或没有method信息") log.error(Template("调试用例没有url信息或没有method信息").substitute()) raise Exception('调试用例没有url信息或没有method信息') self.api_infos = [json_conten] elif isinstance(json_conten, list): for api in json_conten: if not api.get("url") and not api.get('method'): print( Template("$api调试用例没有url信息或没有method信息").substitute( api=api)) log.error( Template("$api调试用例没有url信息或没有method信息").substitute( api=api)) raise Exception( Template("$api调试用例没有url信息或没有method信息").substitute( api=api)) self.api_infos = json_conten else: print( Template("调试文件格式不符合json格式,解析结果异常,请检查!解析后的结果:$r").substitute( r=json_conten)) log.error( Template("调试文件格式不符合json格式,解析结果异常,请检查!解析后的结果:$r").substitute( r=json_conten)) raise Exception( Template('调试文件格式不符合json格式,解析结果异常,请检查!解析后的结果:$r').substitute( r=json_conten)) return json_conten
def kill_process_by_port_on_win(cls, port): find_cmd = 'netstat -aon|findstr "{}"'.format(port) kill_cmd = "taskkill -pid {} -f" with os.popen(find_cmd) as res: res = res.read().split('\n') for line in res: temp = [i for i in line.split(' ') if i != ''] log.debug('the netstat info is: {}'.format(temp)) if len(temp) > 4: addr = temp[1] if str(addr.split(':')[1]) == str(port): pid = temp[4] log.info( 'find the port, the corresponding PID is: {}, and kill the PID process' .format(pid)) os.system(kill_cmd.format(pid)) return log.info('No corresponding port process was found') return
def check_response_header(fields, headers): error_info = "" if not fields: return error_info log.debug( Template("fields: $fields, headers: $headers").substitute( fields=fields, headers=headers)) if not fields: return error_info if not isinstance(fields, str): log.debug(type(fields)) log.debug(fields) raise TypeError("neet to check fields is not a str") for text in fields.split(','): text = text.lstrip("'").lstrip('"').rstrip("'").rstrip('"') if isinstance(headers, dict): # if not headers.get(text): isempty = jmespath.search(text, headers) if isempty is None: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=headers) else: if text not in headers: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=headers) return error_info
def check_response_header(fields, headers): error_info = "" if not fields: return error_info log.debug( Template("fields: $fields, headers: $headers").substitute( fields=fields, headers=headers)) if not fields: return error_info if isinstance(fields, str): log.debug('is a string fields, value is:'.format(fields)) fs = fields.split(',') elif isinstance(fields, list): log.debug('is a list fields, value is: '.format(str(fields))) fs = fields else: log.error('fields param type error') raise TypeError('fields param type error') for text in fs: text = text.lstrip("'").lstrip('"').rstrip("'").rstrip('"') if text: if isinstance(headers, dict): # if not headers.get(text): isempty = jmespath.search(text, headers) if isempty is None: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=headers) else: if text not in headers: error_info += Template( "接口响应内容没有待校验的字段:$t,响应内容:$res").substitute(t=text, res=headers) return error_info
def api_request(self): api_infos = self.api_infos # log.debug(Template("api_infos: $api").substitute(api=api_infos)) index = 0 for api_info in api_infos: index += 1 try: log.debug("正在执行用例:{}".format(index)) res = ApiHandler(api_info).api_request() except Exception as e: log.error( Template("用例$i 请求异常,异常信息:$e,用例信息:$api").substitute( i=index, e=e, api=api_info)) continue try: res_json = res.json() except Exception as e: log.error( Template("用例$i 响应结果解析为json格式异常,响应信息:$t,异常信息:$e,用例信息:" "$api").substitute(t=res.text, e=e, api=api_info)) continue flag = True if api_info.get("verify_fields"): msg = eut.check_verify_fields(api_info["verify_fields"], res_json) if msg: log.error(Template("字段校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("字段校验成功").substitute()) if api_info.get("res_text"): msg = eut.check_response_text(api_info['res_text'], res_json) if msg: log.error( Template("响应内容校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("响应内容校验成功").substitute()) if api_info.get("res_header"): msg = eut.check_response_header(api_info['res_header'], res.headers) if msg: log.error( Template("响应头校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("响应头校验成功").substitute()) if api_info.get("status_code"): msg = eut.check_status_code(api_info['status_code'], res.status_code) if msg: log.error( Template("响应状态码校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("响应状态码校验成功").substitute()) if api_info.get("res_time"): msg = eut.check_response_time(api_info['res_time'], res.elapsed.total_seconds()) if msg: log.error( Template("响应时间校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("响应时间校验成功").substitute()) if api_info.get("expression"): msg = eut.check_pyexpression(api_info['expression'], res_json) if msg: log.error( Template("py表达式校验失败,失败信息:$msg").substitute(msg=msg)) flag = False # check.equal(1, 2, msg=msg) else: log.info(Template("py表达式校验成功").substitute()) if not flag: log.info( Template("用例$i 执行失败,用例信息:$api").substitute(i=index, api=api_info))
random_front = generate_random_str() random_back = generate_random_str() try: content = json.dumps(case, ensure_ascii=False) except Exception as e: log.error( string.Template("testcase json dumps fail, error info: $e"). substitute(e=e)) raise VarReplaceException content = re.sub(pattern_front, random_front, content) content = re.sub(pattern_back, random_back, content) # log.debug(string.Template("after replaced random string: $content").substitute(content=content)) pattern = re.compile(r"\{\w+\}") key_words = re.findall(pattern, content) log.debug( string.Template("所有需要进行变量替换的变量为: $key_words").substitute( key_words=key_words)) if key_words: """ 循环遍历所有待替换的字符,如果待替换的字符在传入的对象列表中有对应的属性,则进行替换 否则不作处理 对象替换按传入的对象的下标进行排序,匹配到了之后,退出循环,即取最先匹配到的对象的属性值 """ for key_word in key_words: real_word = key_word.lstrip('{').rstrip('}') for index in range(0, len(object_list)): log.debug( Template("$o对象的interface_vars属性$a").substitute( o=object_list[index].__class__.__name__, a=object_list[index].__dict__.get(
random_front = generate_random_str() random_back = generate_random_str() try: content = json.dumps(case, ensure_ascii=False) except Exception as e: log.error( string.Template("testcase json dumps fail, error info: $e"). substitute(e=e)) raise VarReplaceException content = re.sub(pattern_front, random_front, content) content = re.sub(pattern_back, random_back, content) # log.debug(string.Template("after replaced random string: $content").substitute(content=content)) pattern = re.compile(r"\{\w+\}") key_words = re.findall(pattern, content) log.debug( string.Template("所有需要进行变量替换的变量为: $key_words").substitute( key_words=key_words)) if key_words: """ 循环遍历所有待替换的字符,如果待替换的字符在传入的对象列表中有对应的属性,则进行替换 否则不作处理 对象替换按传入的对象的下标进行排序,匹配到了之后,退出循环,即取最先匹配到的对象的属性值 """ for key_word in key_words: real_word = key_word.lstrip('{').rstrip('}') for index in range(0, len(object_list)): log.debug( Template("$o对象的所有属性$a").substitute( o=object_list[index].__class__.__name__, a=object_list[index].__dict__))