import json
import re
from json.decoder import JSONDecodeError

import allure
import jsonpath

from common.assertions import Assertions
from common.debugtalk import DebugTalk
from common.readyaml import get_testcase_yaml, ReadYamlData
from common.recordlog import logs
from common.sendrequest import SendRequest
from conf.operationConfig import OperationConfig
from conf.setting import FILE_PATH


class RequestBase:

    def __init__(self):
        self.run = SendRequest()
        self.conf = OperationConfig()
        self.read = ReadYamlData()
        self.asserts = Assertions()

    def replace_load(self, data):
        """yaml数据替换解析"""
        str_data = data
        if not isinstance(data, str):
            str_data = json.dumps(data, ensure_ascii=False)
            # print('从yaml文件获取的原始数据:', str_data)
        for i in range(str_data.count('${')):
            if '${' in str_data and '}' in str_data:
                start_index = str_data.index('$')
                end_index = str_data.index('}', start_index)
                ref_all_params = str_data[start_index:end_index + 1]
                # 取出yaml文件的函数名
                func_name = ref_all_params[2:ref_all_params.index("(")]
                # 取出函数里面的参数
                func_params = ref_all_params[ref_all_params.index("(") + 1:ref_all_params.index(")")]
                # 传入替换的参数获取对应的值,类的反射----getattr,setattr,del....
                extract_data = getattr(DebugTalk(), func_name)(*func_params.split(',') if func_params else "")

                if extract_data and isinstance(extract_data, list):
                    extract_data = ','.join(e for e in extract_data)
                str_data = str_data.replace(ref_all_params, str(extract_data))
                # print('通过解析后替换的数据:', str_data)

        # 还原数据
        if data and isinstance(data, dict):
            data = json.loads(str_data)
        else:
            data = str_data
        return data

    def specification_yaml(self, base_info, test_case):
        """
        接口请求处理基本方法
        :param base_info: yaml文件里面的baseInfo
        :param test_case: yaml文件里面的testCase
        :return:
        """
        try:
            params_type = ['data', 'json', 'params']
            url_host = self.conf.get_section_for_data('api_envi', 'host')
            api_name = base_info['api_name']
            allure.attach(api_name, f'接口名称:{api_name}', allure.attachment_type.TEXT)
            url = url_host + base_info['url']
            allure.attach(api_name, f'接口地址:{url}', allure.attachment_type.TEXT)
            method = base_info['method']
            allure.attach(api_name, f'请求方法:{method}', allure.attachment_type.TEXT)
            header = self.replace_load(base_info['header'])
            allure.attach(api_name, f'请求头:{header}', allure.attachment_type.TEXT)
            # 处理cookie
            cookie = None
            if base_info.get('cookies') is not None:
                cookie = eval(self.replace_load(base_info['cookies']))
            case_name = test_case.pop('case_name')
            allure.attach(api_name, f'测试用例名称:{case_name}', allure.attachment_type.TEXT)
            # 处理断言
            val = self.replace_load(test_case.get('validation'))
            test_case['validation'] = val
            validation = eval(test_case.pop('validation'))
            # 处理参数提取
            extract = test_case.pop('extract', None)
            extract_list = test_case.pop('extract_list', None)
            # 处理接口的请求参数
            for key, value in test_case.items():
                if key in params_type:
                    test_case[key] = self.replace_load(value)

            # 处理文件上传接口
            file, files = test_case.pop('files', None), None
            if file is not None:
                for fk, fv in file.items():
                    allure.attach(json.dumps(file), '导入文件')
                    files = {fk: open(fv, mode='rb')}

            res = self.run.run_main(name=api_name, url=url, case_name=case_name, header=header, method=method,
                                    file=files, cookies=cookie, **test_case)
            status_code = res.status_code
            allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)

            try:
                res_json = json.loads(res.text)  # 把json格式转换成字典字典
                if extract is not None:
                    self.extract_data(extract, res.text)
                if extract_list is not None:
                    self.extract_data_list(extract_list, res.text)
                # 处理断言
                self.asserts.assert_result(validation, res_json, status_code)
            except JSONDecodeError as js:
                logs.error('系统异常或接口未请求!')
                raise js
            except Exception as e:
                logs.error(e)
                raise e

        except Exception as e:
            raise e

    @classmethod
    def allure_attach_response(cls, response):
        if isinstance(response, dict):
            allure_response = json.dumps(response, ensure_ascii=False, indent=4)
        else:
            allure_response = response
        return allure_response

    def extract_data(self, testcase_extarct, response):
        """
        提取接口的返回值,支持正则表达式和json提取器
        :param testcase_extarct: testcase文件yaml中的extract值
        :param response: 接口的实际返回值
        :return:
        """
        try:
            pattern_lst = ['(.*?)', '(.+?)', r'(\d)', r'(\d*)']
            for key, value in testcase_extarct.items():

                # 处理正则表达式提取
                for pat in pattern_lst:
                    if pat in value:
                        ext_lst = re.search(value, response)
                        if pat in [r'(\d+)', r'(\d*)']:
                            extract_data = {key: int(ext_lst.group(1))}
                        else:
                            extract_data = {key: ext_lst.group(1)}
                        self.read.write_yaml_data(extract_data)
                # 处理json提取参数
                if '$' in value:
                    ext_json = jsonpath.jsonpath(json.loads(response), value)[0]
                    if ext_json:
                        extarct_data = {key: ext_json}
                        logs.info('提取接口的返回值:', extarct_data)
                    else:
                        extarct_data = {key: '未提取到数据,请检查接口返回值是否为空!'}
                    self.read.write_yaml_data(extarct_data)
        except Exception as e:
            logs.error(e)

    def extract_data_list(self, testcase_extract_list, response):
        """
        提取多个参数,支持正则表达式和json提取,提取结果以列表形式返回
        :param testcase_extract_list: yaml文件中的extract_list信息
        :param response: 接口的实际返回值,str类型
        :return:
        """
        try:
            for key, value in testcase_extract_list.items():
                if "(.+?)" in value or "(.*?)" in value:
                    ext_list = re.findall(value, response, re.S)
                    if ext_list:
                        extract_date = {key: ext_list}
                        logs.info('正则提取到的参数:%s' % extract_date)
                        self.read.write_yaml_data(extract_date)
                if "$" in value:
                    # 增加提取判断,有些返回结果为空提取不到,给一个默认值
                    ext_json = jsonpath.jsonpath(json.loads(response), value)
                    if ext_json:
                        extract_date = {key: ext_json}
                    else:
                        extract_date = {key: "未提取到数据,该接口返回结果可能为空"}
                    logs.info('json提取到参数:%s' % extract_date)
                    self.read.write_yaml_data(extract_date)
        except:
            logs.error('接口返回值提取异常,请检查yaml文件extract_list表达式是否正确!')


if __name__ == '__main__':
    case_info = get_testcase_yaml(FILE_PATH['YAML'] + '/LoginAPI/login.yaml')[0]
    # print(case_info)
    req = RequestBase()
    # res = req.specification_yaml(case_info)
    res = req.specification_yaml(case_info)
    print(res)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.

base木录下apiutil_business.py模块:

-- coding: utf-8 --

sys.path.insert(0, "..")

from common.sendrequest import SendRequest from common.readyaml import ReadYamlData from common.recordlog import logs from conf.operationConfig import OperationConfig from common.assertions import Assertions from common.debugtalk import DebugTalk import allure import json import jsonpath import re import traceback from json.decoder import JSONDecodeError

assert_res = Assertions()

class RequestBase(object): def init(self): self.run = SendRequest() self.read = ReadYamlData() self.conf = OperationConfig()

def handler_yaml_list(self, data_dict):
    """处理yaml文件测试用例请求参数为list情况,以数组形式"""
    try:
        for key, value in data_dict.items():
            if isinstance(value, list):
                value_lst = ','.join(value).split(',')
                data_dict[key] = value_lst
            return data_dict
    except Exception:
        logs.error(str(traceback.format_exc()))

def replace_load(self, data):
    """yaml数据替换解析"""
    str_data = data
    if not isinstance(data, str):
        str_data = json.dumps(data, ensure_ascii=False)
    for i in range(str_data.count('${')):
        if '${' in str_data and '}' in str_data:
            # index检测字符串是否子字符串,并找到字符串的索引位置
            start_index = str_data.index('$')
            end_index = str_data.index('}', start_index)
            # yaml文件的参数,如:${get_yaml_data(loginname)}
            ref_all_params = str_data[start_index:end_index + 1]
            # 函数名,获取Debugtalk的方法
            func_name = ref_all_params[2:ref_all_params.index("(")]
            # 函数里的参数
            func_params = ref_all_params[ref_all_params.index("(") + 1:ref_all_params.index(")")]
            # 传入替换的参数获取对应的值,*func_params按,分割重新得到一个字符串
            extract_data = getattr(DebugTalk(), func_name)(*func_params.split(',') if func_params else "")
            if extract_data and isinstance(extract_data, list):
                extract_data = ','.join(e for e in extract_data)
            str_data = str_data.replace(ref_all_params, str(extract_data))
    # 还原数据
    if data and isinstance(data, dict):
        data = json.loads(str_data)
        self.handler_yaml_list(data)
    else:
        data = str_data
    return data

def specification_yaml(self, case_info):
    """
    规范yaml测试用例的写法
    :param case_info: list类型,调试取case_info[0]-->dict
    :return:
    """
    params_type = ['params', 'data', 'json']
    cookie = None
    try:
        base_url = self.conf.get_section_for_data('api_envi', 'host')
        # base_url = self.replace_load(case_info['baseInfo']['url'])
        url = base_url + case_info["baseInfo"]["url"]
        allure.attach(url, f'接口地址:{url}')
        api_name = case_info["baseInfo"]["api_name"]
        allure.attach(api_name, f'接口名:{api_name}')
        method = case_info["baseInfo"]["method"]
        allure.attach(method, f'请求方法:{method}')
        header = self.replace_load(case_info["baseInfo"]["header"])
        allure.attach(str(header), '请求头信息', allure.attachment_type.TEXT)
        try:
            cookie = self.replace_load(case_info["baseInfo"]["cookies"])
            allure.attach(str(cookie), 'Cookie', allure.attachment_type.TEXT)
        except:
            pass
        for tc in case_info["testCase"]:
            case_name = tc.pop("case_name")
            allure.attach(case_name, f'测试用例名称:{case_name}', allure.attachment_type.TEXT)
            # 断言结果解析替换
            val = self.replace_load(tc.get('validation'))
            tc['validation'] = val
            # 字符串形式的列表转换为list类型
            validation = eval(tc.pop('validation'))
            allure_validation = str([str(list(i.values())) for i in validation])
            allure.attach(allure_validation, "预期结果", allure.attachment_type.TEXT)
            extract = tc.pop('extract', None)
            extract_lst = tc.pop('extract_list', None)
            for key, value in tc.items():
                if key in params_type:
                    tc[key] = self.replace_load(value)
            file, files = tc.pop("files", None), None
            if file is not None:
                for fk, fv in file.items():
                    allure.attach(json.dumps(file), '导入文件')
                    files = {fk: open(fv, 'rb')}
            res = self.run.run_main(name=api_name,
                                    url=url,
                                    case_name=case_name,
                                    header=header,
                                    cookies=cookie,
                                    method=method,
                                    file=files, **tc)
            res_text = res.text
            allure.attach(res_text, '接口响应信息', allure.attachment_type.TEXT)
            status_code = res.status_code
            allure.attach(self.allure_attach_response(res.json()), '接口响应信息', allure.attachment_type.TEXT)

            try:
                res_json = json.loads(res_text)
                if extract is not None:
                    self.extract_data(extract, res_text)
                if extract_lst is not None:
                    self.extract_data_list(extract_lst, res_text)
                # 处理断言
                assert_res.assert_result(validation, res_json, status_code)
            except JSONDecodeError as js:
                logs.error("系统异常或接口未请求!")
                raise js
            except Exception as e:
                logs.error(str(traceback.format_exc()))
                raise e
    except Exception as e:
        logs.error(e)
        raise e

@classmethod
def allure_attach_response(cls, response):
    if isinstance(response, dict):
        allure_response = json.dumps(response, ensure_ascii=False, indent=4)
    else:
        allure_response = response
    return allure_response

def extract_data(self, testcase_extract, response):
    """
    提取接口的返回参数,支持正则表达式和json提取,提取单个参数
    :param testcase_extract: testcase文件yaml中的extract值
    :param response: 接口的实际返回值,str类型
    :return:
    """
    pattern_lst = ['(.+?)', '(.*?)', r'(\d+)', r'(\d*)']
    try:
        for key, value in testcase_extract.items():
            for pat in pattern_lst:
                if pat in value:
                    ext_list = re.search(value, response)
                    if pat in [r'(\d+)', r'(\d*)']:
                        extract_date = {key: int(ext_list.group(1))}
                    else:
                        extract_date = {key: ext_list.group(1)}
                    logs.info('正则提取到的参数:%s' % extract_date)
                    self.read.write_yaml_data(extract_date)
            if "$" in value:
                ext_json = jsonpath.jsonpath(json.loads(response), value)[0]
                if ext_json:
                    extract_date = {key: ext_json}
                else:
                    extract_date = {key: "未提取到数据,该接口返回结果可能为空"}
                logs.info('json提取到参数:%s' % extract_date)
                self.read.write_yaml_data(extract_date)
    except:
        logs.error('接口返回值提取异常,请检查yaml文件extract表达式是否正确!')

def extract_data_list(self, testcase_extract_list, response):
    """
    提取多个参数,支持正则表达式和json提取,提取结果以列表形式返回
    :param testcase_extract_list: yaml文件中的extract_list信息
    :param response: 接口的实际返回值,str类型
    :return:
    """
    try:
        for key, value in testcase_extract_list.items():
            if "(.+?)" in value or "(.*?)" in value:
                ext_list = re.findall(value, response, re.S)
                if ext_list:
                    extract_date = {key: ext_list}
                    logs.info('正则提取到的参数:%s' % extract_date)
                    self.read.write_yaml_data(extract_date)
            if "$" in value:
                # 增加提取判断,有些返回结果为空提取不到,给一个默认值
                ext_json = jsonpath.jsonpath(json.loads(response), value)
                if ext_json:
                    extract_date = {key: ext_json}
                else:
                    extract_date = {key: "未提取到数据,该接口返回结果可能为空"}
                logs.info('json提取到参数:%s' % extract_date)
                self.read.write_yaml_data(extract_date)
    except:
        logs.error('接口返回值提取异常,请检查yaml文件extract_list表达式是否正确!')
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.

 base下的generateld.py

def generate_module_id():
    """
    生成测试模块编号,为了保证allure报告的顺序与pytest设定的执行顺序保持一致
    :return:
    """
    for i in range(1, 1000):
        module_id = 'M' + str(i).zfill(2) + '_'
        yield module_id


def generate_testcase_id():
    """
    生成测试用例编号
    :return:
    """
    for i in range(1, 10000):
        case_id = 'C' + str(i).zfill(2) + '_'
        yield case_id


m_id = generate_module_id()
c_id = generate_testcase_id()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

base下的newtest_case.py

import datetime
import os
import cgitb
from PyQt5.uic import loadUi
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
from PyQt5 import QtGui, QtWidgets
import json
import traceback
import yaml
import requests
from PyQt5 import QtCore
from hashlib import sha1
import base64
import urllib3

cgitb.enable(format='text')


class LogThread(QThread):
    # 设置线程变量
    trigger = pyqtSignal(str)

    def __int__(self, parent=None):
        super(LogThread, self).__init__(parent)

    def run_(self, message):
        """向信号trigger发送消息"""
        self.trigger.emit(message)


class NewTestCaseTools(QtWidgets.QMainWindow):

    def __init__(self, parent=None):
        super(NewTestCaseTools, self).__init__(parent)
        self.setupUI()

        # self.threads = LogThread(self)  # 自定义线程类
        # self.threads.trigger.connect(self.update_text)  # 当信号接收到消息时,更新数据
        # 初始化子窗口
        self.child_win = ToolMD5Window()
        self.child_win_base64 = ToolBase64Window()
        self.child_win_sha1 = ToolSha1Window()

    def setupUI(self):
        loadUi('./new_tools.ui', self)
        # 一些控件的设置可在此方法设置
        self.controls_setting()
        self.set_api_name()
        self.set_url()
        self.set_methods()
        self.set_requests_header()
        self.set_request_params()
        self.set_testcase_name()
        # self.set_assert_type()
        self.set_assert_params()
        self.set_depend_type()
        self.set_extract_data_type()
        self.set_depend_extract_params()
        # self.testcase_template()
        # self.create_testcase_directory()
        self.other_func()
        self.get_files()
        self.get_assert_params()
        self.radioButton.setChecked(True)
        self.radioButton_6.setChecked(True)
        self.get_depend_params()
        self.menu.setTitle("系统设置")
        self.menu_2.setTitle("编辑")
        self.menu_3.setTitle("帮助")
        self.actionhostpeizhi.setText("host设置")
        self.actionmorenqingqiutou.setText("默认请求头设置")
        self.actionbangzhuxinxi.setText("帮助信息")

    def closeEvent(self, event):
        """关闭窗口弹框"""
        reply = QtWidgets.QMessageBox.question(self, '警告', '退出后测试将停止,\n你确认要退出吗?',
                                               QtWidgets.QMessageBox.Yes, QtWidgets.QMessageBox.No)
        if reply == QtWidgets.QMessageBox.Yes:
            # self.outputToFile()  # 关闭程序时自动保存控制台log
            event.accept()
        else:
            event.ignore()

    # def __reset_button(self):
    #     loadUi('./new_tools.ui', self)

    def bounced(self, text, title='提示'):
        """
        提示弹框
        :param text: 提示框内容
        :param title: 提示框标题,默认为”提示“
        :return:
        """

        try:
            quitMsgBox = QMessageBox()
            quitMsgBox.setWindowTitle(title)
            quitMsgBox.setText(text)
            # 设置自定义弹框大小
            # quitMsgBox.resize(400, 300)
            buttonY = QPushButton('确定')
            quitMsgBox.addButton(buttonY, QMessageBox.YesRole)
            quitMsgBox.exec_()
            buttonY.close()
        except Exception as e:
            print(e)

    def set_api_name(self):
        self.lineEdit.setClearButtonEnabled(True)

    def get_api_name(self):
        """获取接口名"""
        api_name = self.lineEdit.text().strip()
        if api_name == "":
            self.bounced("接口名不能为空")
        return api_name

    def set_url(self):
        self.lineEdit_2.setClearButtonEnabled(True)

    def get_url(self):
        """url"""
        url = self.lineEdit_2.text()
        if url == "":
            self.bounced("url不能为空")
        return url

    def set_methods(self):
        # 添加一个下拉选项
        method = ['GET', 'POST', 'PUT', 'DELETE']
        for m in method:
            self.comboBox.addItem(m)

    def get_method(self):
        """获取请求方法"""
        method = self.comboBox.currentText()
        return method

    def set_requests_header(self):
        # 设置表格宽度
        self.tableWidget.setColumnWidth(0, 120)
        self.tableWidget.setColumnWidth(1, 220)
        self.tableWidget.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        # 设置单元格高度
        self.tableWidget.setRowHeight(0, 50)
        self.tableWidget.setRowHeight(1, 50)
        self.pushButton.clicked.connect(lambda: self.add_table_row(self.tableWidget))
        self.pushButton_2.clicked.connect(lambda: self.delete_table_row(self.tableWidget))

    def get_header(self):
        tables_header_data = {}
        table_rows = self.tableWidget.rowCount()
        table_cols = self.tableWidget.columnCount()
        # 存储表格数据
        for i in range(0, table_rows):
            for j in range(0, table_cols):
                # 字典的列索引值固定
                tables_header_data[self.tableWidget.item(i, 0).text()] = self.tableWidget.item(i, j).text()
        if not tables_header_data:
            tables_header_data = ''

        return tables_header_data

    def add_table_row(self, table_widget):
        cur_count = table_widget.rowCount()
        cur_count += 1
        table_widget.setRowCount(cur_count)

    def delete_table_row(self, table_widget):
        row_items = table_widget.selectedItems()
        if row_items:
            # 求出所选择的行数
            selected_rows = []
            for i in row_items:
                row = i.row()
                if row not in selected_rows:
                    selected_rows.append(row)
            for r in range(len(sorted(selected_rows))):
                table_widget.removeRow(selected_rows[r] - r)  # 删除行

    def get_tables_data(self, table_widget):
        tables_data = {}
        table_rows = table_widget.rowCount()  # 获取表格行数
        table_cols = table_widget.columnCount()  # 获取表格列数
        # 存储表格数据
        for i in range(0, table_rows):
            for j in range(0, table_cols):
                # 字典的列索引值固定
                tables_data[table_widget.item(i, 0).text()] = table_widget.item(i, j).text()

        return tables_data

    def set_testcase_name(self):
        self.lineEdit_3.setClearButtonEnabled(True)

    def case_name(self):
        case_name = self.lineEdit_3.text().strip()
        if case_name == "":
            self.bounced("用例名不能为空")
        return case_name

    def set_request_params(self):
        # 设置tab标签页名,根据索引设置,从0起始
        self.tabWidget.setTabText(0, 'params')
        self.tabWidget.setTabText(1, 'form-data')
        self.tabWidget.setTabText(2, 'json')
        self.tabWidget.setTabText(3, 'files')
        # 设置tab页1--params
        self.tableWidget_2.setColumnWidth(0, 120)
        self.tableWidget_2.setColumnWidth(1, 220)
        self.tabWidget.setCurrentIndex(0)
        # 表格表头设置外框线
        self.tableWidget_2.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        # 设置单元格高度
        self.tableWidget_2.setRowHeight(0, 50)
        self.tableWidget_2.setRowHeight(1, 50)
        self.pushButton_4.clicked.connect(lambda: self.add_table_row(self.tableWidget_2))
        self.pushButton_5.clicked.connect(lambda: self.delete_table_row(self.tableWidget_2))
        # 设置tab页2--from-data
        self.tableWidget_3.setColumnWidth(0, 120)
        self.tableWidget_3.setColumnWidth(1, 220)
        self.tableWidget_3.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        # 设置单元格高度
        self.tableWidget_3.setRowHeight(0, 50)
        self.tableWidget_3.setRowHeight(1, 50)
        self.pushButton_6.clicked.connect(lambda: self.add_table_row(self.tableWidget_3))
        self.pushButton_7.clicked.connect(lambda: self.delete_table_row(self.tableWidget_3))
        # 设置tab页3--json
        self.textEdit.setAcceptRichText(False)
        # 设置tab页4--files
        self.tableWidget_4.setColumnWidth(0, 120)
        self.tableWidget_4.setColumnWidth(1, 220)
        self.tableWidget_4.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        self.lineEdit_9.setPlaceholderText('参数名')
        self.lineEdit_9.setClearButtonEnabled(True)
        self.lineEdit_10.setPlaceholderText('文件路径')
        self.lineEdit_10.setClearButtonEnabled(True)
        # 设置单元格高度
        self.tableWidget_4.setRowHeight(0, 50)
        self.tableWidget_4.setRowHeight(1, 50)
        self.pushButton_8.clicked.connect(lambda: self.add_table_row(self.tableWidget_4))
        self.pushButton_9.clicked.connect(lambda: self.delete_table_row(self.tableWidget_4))
        self.pushButton_3.clicked.connect(self.open_file)

    def request_params(self):
        params_type = self.tabWidget.tabText(self.tabWidget.currentIndex())
        tables_request_data = {}
        if params_type == 'params':
            table_rows = self.tableWidget_2.rowCount()
            table_cols = self.tableWidget_2.columnCount()
            # 存储表格数据
            for i in range(0, table_rows):
                for j in range(0, table_cols):
                    if self.tableWidget_2.item(i, j) is None:
                        tables_request_data[self.tableWidget_2.item(i, 0).text()] = ''
                    else:
                        tables_request_data[self.tableWidget_2.item(i, 0).text()] = self.tableWidget_2.item(i, j).text()

        elif params_type == 'form-data':
            params_type = 'data'
            table_rows = self.tableWidget_3.rowCount()
            table_cols = self.tableWidget_3.columnCount()
            for i in range(0, table_rows):
                for j in range(0, table_cols):
                    if self.tableWidget_3.item(i, j) is None:
                        tables_request_data[self.tableWidget_3.item(i, 0).text()] = ''
                    else:
                        tables_request_data[self.tableWidget_3.item(i, 0).text()] = self.tableWidget_3.item(i, j).text()
        elif params_type == 'json':
            text = self.textEdit.toPlainText()
            if text:
                tables_request_data = json.loads(text)

        elif params_type == 'files':
            table_rows = self.tableWidget_4.rowCount()
            table_cols = self.tableWidget_4.columnCount()
            for i in range(0, table_rows):
                for j in range(0, table_cols):
                    # 表格设置值
                    # self.tableWidget_4.setItem(i, 1, QTableWidgetItem(file_name[0]))
                    tables_request_data[self.tableWidget_4.item(i, 0).text()] = self.tableWidget_4.item(i, j).text()
            file_param = self.lineEdit_9.text()
            file_name = self.lineEdit_10.text()

            if file_name != '':
                tables_request_data = {file_param: file_name}
            else:
                return tables_request_data
        if not tables_request_data:
            tables_request_data = ''

        return params_type, tables_request_data

    def tab_click(self):
        """tab标签点击事件"""
        # self.tabWidget.currentChanged.connect(self.request_params)

    def get_files(self):
        files_dict = {}
        table_rows = self.tableWidget_4.rowCount()
        table_cols = self.tableWidget_4.columnCount()
        for i in range(0, table_rows):
            for j in range(0, table_cols):
                files_dict[self.tableWidget_4.item(i, 0).text()] = self.tableWidget_4.item(i, j).text()

        return files_dict

    def set_assert_type(self):
        """断言类型,返回id属性"""
        # 设置单选默认选项

    def set_assert_params(self):
        self.tableWidget_5.setColumnWidth(0, 120)
        self.tableWidget_5.setColumnWidth(1, 220)
        # 设置单元格高度
        self.tableWidget_5.setRowHeight(0, 50)
        self.tableWidget_5.setRowHeight(1, 50)
        self.tableWidget_5.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        self.pushButton_11.clicked.connect(lambda: self.add_table_row(self.tableWidget_5))
        self.pushButton_10.clicked.connect(lambda: self.delete_table_row(self.tableWidget_5))

    def get_assert_params(self):
        assert_list = []
        assert_type = None
        # 新建组
        self.radioButtonGroup_1 = QtWidgets.QButtonGroup(self.groupBox_2)
        # 将第一组单选按钮加入到分组中,并设置按钮id
        self.radioButtonGroup_1.addButton(self.radioButton, 1001)
        self.radioButtonGroup_1.addButton(self.radioButton_2, 1002)
        assert_id = self.radioButtonGroup_1.checkedId()

        if assert_id == 1001:
            assert_type = "contains"
        elif assert_id == 1002:
            assert_type = "equal"

        tables_data = {}
        table_rows = self.tableWidget_5.rowCount()  # 获取表格行数
        table_cols = self.tableWidget_5.columnCount()  # 获取表格列数
        # 存储表格数据
        for i in range(0, table_rows):
            for j in range(0, table_cols):
                # 字典的列索引值固定
                tables_data[self.tableWidget_5.item(i, 0).text()] = self.tableWidget_5.item(i, j).text()

        for key, value in tables_data.items():
            assert_list.append({assert_type: {key: value}})

        if not assert_list:
            assert_list = ''

        return assert_list

    def set_depend_type(self):
        # 设置单选按钮默认选中项
        self.radioButton_4.setChecked(True)

    def set_extract_data_type(self):
        self.radioButtonGroup_3 = QtWidgets.QButtonGroup(self.groupBox_2)
        # 将第一组单选按钮加入到分组中,并设置按钮id
        self.radioButtonGroup_3.addButton(self.radioButton_3, 1003)
        self.radioButtonGroup_3.addButton(self.radioButton_4, 1004)

    def set_depend_extract_params(self):
        self.tableWidget_6.setColumnWidth(0, 120)
        self.tableWidget_6.setColumnWidth(1, 220)
        self.tableWidget_6.horizontalHeader().setStyleSheet(
            "color: rgb(0, 83, 128);border:1px solid rgb(210, 210, 210);")
        # 设置单元格高度
        self.tableWidget_6.setRowHeight(0, 50)
        self.tableWidget_6.setRowHeight(1, 50)
        self.pushButton_13.clicked.connect(lambda: self.add_table_row(self.tableWidget_6))
        self.pushButton_12.clicked.connect(lambda: self.delete_table_row(self.tableWidget_6))

    def get_depend_params(self):
        extract_dict = {}
        self.radioButtonGroup_2 = QtWidgets.QButtonGroup(self.groupBox_2)
        # 将第一组单选按钮加入到分组中,并设置按钮id
        self.radioButtonGroup_2.addButton(self.radioButton_6, 1005)
        self.radioButtonGroup_2.addButton(self.radioButton_5, 1006)

        extract_type_id = self.radioButtonGroup_2.checkedId()

        tables_data = {}
        table_rows = self.tableWidget_6.rowCount()  # 获取表格行数
        table_cols = self.tableWidget_6.columnCount()  # 获取表格列数
        for i in range(0, table_rows):
            for j in range(0, table_cols):
                # 字典的列索引值固定
                tables_data[self.tableWidget_6.item(i, 0).text()] = self.tableWidget_6.item(i, j).text()

        if extract_type_id == 1005:
            extract_dict = {
                'extract': tables_data
            }
        elif extract_type_id == 1006:
            extract_dict = {
                'extract_list': tables_data
            }

        return extract_dict

    def controls_setting(self):
        self.lineEdit_6.setClearButtonEnabled(True)
        self.lineEdit_5.setClearButtonEnabled(True)
        self.lineEdit_4.setClearButtonEnabled(True)
        self.textEdit_2.setReadOnly(True)
        self.lineEdit_7.setClearButtonEnabled(True)
        self.lineEdit_8.setClearButtonEnabled(True)
        # 设置单选默认选择项
        # self.radioButton_6.setChecked(True)
        # 设置窗口名称
        self.setWindowTitle("测试用例生成工具V2.0")
        # 设置输入框提示
        self.lineEdit_2.setPlaceholderText("填接口地址即可,不需要ip和port")
        self.lineEdit_4.setPlaceholderText("测试用例文件的存储目录")
        # 工具栏
        self.actionMD5jia.setText("MD5加密")
        self.actionbase64.setText("base64加密")
        self.actionsha1.setText("sha1加密")

    def load_directory(self):
        BASE_DIR = os.path.dirname(os.path.dirname(__file__))
        base_testCase = os.path.join(BASE_DIR, 'testcase')
        if not os.path.exists(base_testCase):
            os.mkdir(base_testCase)
        else:
            pass
        return base_testCase

    def test_case_filename(self):
        case_filename = self.lineEdit_6.text().strip()

        if case_filename == "":
            self.bounced("测试用例文件名不能为空!")
        return case_filename

    def open_file(self):
        # file_name返回的是一个元组,第一个参数为文件名,第二个为文件类型
        file_name = QFileDialog.getOpenFileName(self, "选取文件", "./", "All Files (*);;Excel Files (*.xls)")
        # return file_name
        self.lineEdit_10.setText(file_name[0])

    def open_generate_file(self):
        file_name = QFileDialog.getExistingDirectory(self, "选择测试用例存储路径", self.load_directory())
        self.lineEdit_5.setText(file_name)

    def create_testcase_directory(self):
        root_dir = self.load_directory()
        creat_dir = self.lineEdit_4.text()

        test_case_filepth = root_dir + "\\" + creat_dir
        # 判断路径是否存在
        isExists = os.path.exists(test_case_filepth)
        if not isExists:
            os.mkdir(test_case_filepth)
            self.logging_out(self.info_log_text('目录【%s】创建成功!' % creat_dir))
        else:
            self.logging_out(self.error_log_text('目录:"%s" 已存在' % test_case_filepth))

        return test_case_filepth

    def get_current_time(self):
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return current_time

    def info_log_text(self, msg):
        current_time = self.get_current_time()
        self.textEdit_2.setStyleSheet('background-color: white;'
                                      'color: green;'
                                      'font: normal 11pt "Times New Roman";')
        logs_msg = str(current_time) + "-" + "[INFO]" + "-" + str(msg)
        return logs_msg

    def error_log_text(self, msg):
        current_time = self.get_current_time()
        self.textEdit_2.setStyleSheet('background-color: white;'
                                      'color: red;'
                                      'font: normal 11pt "Times New Roman";')
        logs_msg = str(current_time) + "-" + "[ERROR]" + "-" + str(msg)
        return logs_msg

    def write_yaml_data(self, filepath, data):
        # allow_unicode=True:处理中文
        test_file_name = self.test_case_filename()
        try:
            with open(filepath, 'w', encoding='utf-8') as f:
                if test_file_name != '':
                    yaml.dump(data, f, allow_unicode=True, sort_keys=False)
                    self.logging_out(self.info_log_text("测试用例文件生成成功!"))
        except Exception:
            self.logging_out(self.error_log_text(str(traceback.format_exc())))

    def testcase_baseinfo_template(self):
        header = self.get_header()
        if header != "":
            base_info = {
                'api_name': self.get_api_name(),
                'url': self.get_url(),
                'method': self.get_method(),
                'header': header
            }
        else:
            base_info = {
                'api_name': self.get_api_name(),
                'url': self.get_url(),
                'method': self.get_method(),
            }
        return base_info

    def testcase_template(self):
        self.radioButtonGroup_3 = QtWidgets.QButtonGroup(self.groupBox_2)
        # 将第一组单选按钮加入到分组中,并设置按钮id

        self.radioButtonGroup_3.addButton(self.radioButton_3, 1003)
        self.radioButtonGroup_3.addButton(self.radioButton_4, 1004)
        # 获取依赖类型属性id
        depend_id = self.radioButtonGroup_3.checkedId()
        depend_params = self.get_depend_params()

        try:
            testcase = {
                1003: [{
                    'case_name': self.case_name(),
                    self.request_params()[0]: self.request_params()[1],
                    'validation': self.get_assert_params(),
                    ''.join(list(depend_params.keys())): list(depend_params.values())[0]
                }],
                1004: [{
                    'case_name': self.case_name(),
                    self.request_params()[0]: self.request_params()[1],
                    'validation': self.get_assert_params()
                }]
            }

            testcase_data = testcase[depend_id]
            # 判断files标签页中table表格中是否有数据,有的话追加到字典中
            file_tab = self.get_files()
            if file_tab:
                for value in testcase.values():
                    value[0]['data'] = file_tab

            return testcase_data

        except Exception:
            self.logging_out(self.error_log_text(str(traceback.format_exc())))

    def all_template(self):
        base_info = self.testcase_baseinfo_template()
        test_case = self.testcase_template()
        base_info_all = [
            {
                'baseInfo': base_info,
                'testCase': test_case
            }
        ]
        return base_info_all

    def generate_testcase_file(self):
        test_file_path = self.lineEdit_5.text().strip()
        test_file_name = self.test_case_filename()
        test_data = self.all_template()
        path_join = test_file_path + '\\' + test_file_name + '.yaml'
        self.write_yaml_data(path_join, test_data)

    def logging_out(self, text):
        # 获取文本光标对象
        cursor = self.textEdit_2.textCursor()
        # 设置字体颜色
        # self.textEdit_2.setStyleSheet('background-color: white;'
        #                               'color: %s;'
        #                               'font: bold italic 11pt "Times New Roman";color:green;')
        # 移动光标
        cursor.movePosition(QtGui.QTextCursor.End)
        # 输出日志换行
        self.textEdit_2.append(cursor.insertText(text))
        # 把文本光标设置回去
        self.textEdit_2.setTextCursor(cursor)
        self.textEdit_2.ensureCursorVisible()

    def update_text(self, message):
        """更新日志信息"""
        self.textEdit_2.append(message)

    def get_host(self):
        ip = self.lineEdit_7.text()
        return ip

    def get_port(self):
        port = self.lineEdit_8.text()
        return port

    def api_debug_button(self):
        method = self.get_method()
        ip = self.get_host()
        port = self.get_port()
        url_text = self.get_url()
        protocol = self.comboBox_2.currentText()
        if port != "":
            url = protocol + '://' + ip + ':' + port + url_text
        else:
            url = protocol + '://' + ip + url_text
        header = self.get_header()
        if header:
            header = header
        handle_params = self.request_params()[1]
        param_type = self.request_params()[0]
        request_param = {param_type: handle_params}
        files = self.get_files()
        try:
            requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
            res = requests.request(url=url, method=method, headers=header, files=files, verify=False, **request_param)
            self.bounced(res.text, title='接口请求结果')
        except Exception:
            self.logging_out(self.error_log_text(str(traceback.format_exc())))

    def clear_log(self):
        """清空日志"""
        self.textEdit_2.clear()

    def other_func(self):
        self.pushButton_14.clicked.connect(self.create_testcase_directory)
        self.pushButton_15.clicked.connect(self.open_generate_file)
        self.pushButton_17.clicked.connect(self.generate_testcase_file)
        self.pushButton_16.clicked.connect(self.api_debug_button)
        self.pushButton_19.clicked.connect(self.clear_log)
        # 点击工具菜单栏触发弹窗事件
        self.actionMD5jia.triggered.connect(self.open_md5_window)
        self.actionbase64.triggered.connect(self.open_base64_window)
        self.actionsha1.triggered.connect(self.open_sha1_window)

    def open_md5_window(self):
        """打开md5加密子窗口"""
        # 显示子窗口
        self.child_win.show()
        # 连接信号
        self.child_win._signal.connect(self.get_md5_data)

    def open_base64_window(self):
        """打开base64加密子窗口"""
        # 显示子窗口
        self.child_win_base64.show()
        # 连接信号
        self.child_win_base64._signal.connect(self.get_base64_data)

    def open_sha1_window(self):
        """打开base64加密子窗口"""
        # 显示子窗口
        self.child_win_sha1.show()
        # 连接信号
        self.child_win_sha1._signal.connect(self.get_sha1_data)

    def get_md5_data(self):
        """主窗口获取子窗口的值"""
        pass

    def get_base64_data(self):
        """主窗口获取子窗口的值"""
        pass

    def get_sha1_data(self):
        """主窗口获取子窗口的值"""
        pass


# 界面样式设置
stylesheet = """
/* QPushButton#xxx或者#xx都表示通过设置的objectName来指定 */
QPushButton#pushButton_17{
     background-color: #76a5af; /*背景颜色*/
     font: bold normal 11pt;
     border-radius: 10px; /*圆角*/
}
#pushButton_17:hover {
    background-color: #648f98; /*鼠标悬停时背景颜色*/
}
QPushButton#pushButton_16{
     background-color: #76a5af; /*背景颜色*/
     font: bold normal 11pt;
     border-radius: 10px; /*圆角*/
}
#pushButton_16:hover {
    background-color: #648f98; /*鼠标悬停时背景颜色*/
}
QLineEdit{
     border-radius: 5px; /*圆角*/
     border:0.5px solid;
}
QComboBox{
     border-radius: 5px; /*圆角*/
     border:0.5px solid;
}
QPushButton#pushButton_18{
     background-color: #76a5af; /*背景颜色*/
     font: bold normal 11pt;
     border-radius: 10px; /*圆角*/
}
#pushButton_18:hover {
    background-color: #648f98; /*鼠标悬停时背景颜色*/
}
QPushButton#pushButton_14{
     background-color: #76a5af; /*背景颜色*/
     font: bold normal 11pt;
     border-radius: 5px; /*圆角*/
}
#pushButton_14:hover {
    background-color: #648f98; /*鼠标悬停时背景颜色*/
}
QPushButton#pushButton_15{
     background-color: #76a5af; /*背景颜色*/
     border-radius: 5px; /*圆角*/
}
#pushButton_15:hover {
    background-color: #648f98; /*鼠标悬停时背景颜色*/
}
#pushButton_3{
     background-color: #cfdff9; /*背景颜色*/
     border-radius: 5px; /*圆角*/
}
#pushButton_3:hover {
    background-color: #acc7f4; /*鼠标悬停时背景颜色*/
}
/*界面背景色设置*/
QWidget#MainWindow{
    background-color:#f6f8fd;
}
"""


# 子窗口-MD5
class ToolMD5Window(QtWidgets.QMainWindow):
    # 自定义信号(str类型),必须在init初始化前定义
    _signal = pyqtSignal(str, str)

    def __init__(self):
        super(ToolMD5Window, self).__init__()
        self.setupUI(self)

    def setupUI(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(583, 385)
        # 设置窗体属性,子窗口弹出后,父窗口不可操作
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 10, 551, 331))
        self.groupBox_3.setObjectName("groupBox_3")
        self.groupBox = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox.setGeometry(QtCore.QRect(0, 20, 541, 131))
        self.groupBox.setObjectName("groupBox")
        self.textEdit = QtWidgets.QTextEdit(self.groupBox)
        self.textEdit.setGeometry(QtCore.QRect(10, 20, 521, 101))
        self.textEdit.setObjectName("textEdit")
        self.textEdit.setText("")
        self.groupBox_2 = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox_2.setGeometry(QtCore.QRect(0, 160, 541, 121))
        self.groupBox_2.setObjectName("groupBox_2")
        self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit_2.setGeometry(QtCore.QRect(10, 20, 521, 91))
        self.textEdit_2.setObjectName("textEdit_2")
        self.textEdit_2.setText("")
        self.pushButton = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton.setGeometry(QtCore.QRect(150, 290, 91, 31))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton_2.setGeometry(QtCore.QRect(280, 290, 91, 31))
        self.pushButton_2.setObjectName("pushButton_2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 583, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        # 设置窗口信息
        MainWindow.setWindowTitle("MD5加密")
        self.groupBox_3.setTitle("MD5加密")
        self.groupBox.setTitle("待输出数据")
        self.groupBox_2.setTitle("结果")
        self.pushButton.setText("加密")
        self.pushButton_2.setText("清空")
        self.textEdit_2.setReadOnly(True)
        self.textEdit.setAcceptRichText(False)

        # 点击按钮
        self.pushButton.clicked.connect(self.set_md5_value)
        self.pushButton_2.clicked.connect(self._clear)

    def sha1_encryption(self):
        """参数sha1加密"""
        enc_data = sha1()
        # 获取待输出数据
        params = self.textEdit.toPlainText()
        enc_data.update(params.encode(encoding="utf-8"))
        return enc_data.hexdigest()

    def set_md5_value(self):
        md5_data = self.sha1_encryption()
        self.textEdit_2.setText(md5_data)

    def _clear(self):
        self.textEdit.clear()
        self.textEdit_2.clear()


# 子窗口-base64
class ToolBase64Window(QtWidgets.QMainWindow):
    # 自定义信号(str类型),必须在init初始化前定义
    _signal = pyqtSignal(str, str)

    def __init__(self):
        super(ToolBase64Window, self).__init__()
        self.setupUI(self)

    def setupUI(self, MainWindow):
        MainWindow.setObjectName("MainWindow_2")
        MainWindow.resize(583, 385)
        # 设置窗体属性,子窗口弹出后,父窗口不可操作
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 10, 551, 331))
        self.groupBox_3.setObjectName("groupBox_3")
        self.groupBox = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox.setGeometry(QtCore.QRect(0, 20, 541, 131))
        self.groupBox.setObjectName("groupBox")
        self.textEdit = QtWidgets.QTextEdit(self.groupBox)
        self.textEdit.setGeometry(QtCore.QRect(10, 20, 521, 101))
        self.textEdit.setObjectName("textEdit")
        self.groupBox_2 = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox_2.setGeometry(QtCore.QRect(0, 160, 541, 121))
        self.groupBox_2.setObjectName("groupBox_2")
        self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit_2.setGeometry(QtCore.QRect(10, 20, 521, 91))
        self.textEdit_2.setObjectName("textEdit_2")
        self.pushButton = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton.setGeometry(QtCore.QRect(150, 290, 91, 31))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton_2.setGeometry(QtCore.QRect(280, 290, 91, 31))
        self.pushButton_2.setObjectName("pushButton_2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 583, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        # 设置窗口信息
        MainWindow.setWindowTitle("Base64加密")
        self.groupBox_3.setTitle("Base64加密")
        self.groupBox.setTitle("待输出数据")
        self.groupBox_2.setTitle("结果")
        self.pushButton.setText("加密")
        self.pushButton_2.setText("清空")
        self.textEdit_2.setReadOnly(True)
        self.textEdit.setAcceptRichText(False)

        # 点击按钮
        self.pushButton.clicked.connect(self.set_base64_value)
        self.pushButton_2.clicked.connect(self._clear)

    def base64_encryption(self):
        """base64加密"""
        params = self.textEdit.toPlainText()
        base_params = params.encode("utf-8")
        encr = base64.b64encode(base_params)
        return encr

    def set_base64_value(self):
        base64_data = self.base64_encryption()
        self.textEdit_2.setText(str(base64_data))

    def _clear(self):
        self.textEdit.clear()
        self.textEdit_2.clear()


# 子窗口-sha1加密
class ToolSha1Window(QtWidgets.QMainWindow):
    # 自定义信号(str类型),必须在init初始化前定义
    _signal = pyqtSignal(str, str)

    def __init__(self):
        super(ToolSha1Window, self).__init__()
        self.setupUI(self)

    def setupUI(self, MainWindow):
        MainWindow.setObjectName("MainWindow_3")
        MainWindow.resize(583, 385)
        # 设置窗体属性,子窗口弹出后,父窗口不可操作
        self.setWindowModality(QtCore.Qt.ApplicationModal)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.groupBox_3 = QtWidgets.QGroupBox(self.centralwidget)
        self.groupBox_3.setGeometry(QtCore.QRect(20, 10, 551, 331))
        self.groupBox_3.setObjectName("groupBox_3")
        self.groupBox = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox.setGeometry(QtCore.QRect(0, 20, 541, 131))
        self.groupBox.setObjectName("groupBox")
        self.textEdit = QtWidgets.QTextEdit(self.groupBox)
        self.textEdit.setGeometry(QtCore.QRect(10, 20, 521, 101))
        self.textEdit.setObjectName("textEdit")
        self.groupBox_2 = QtWidgets.QGroupBox(self.groupBox_3)
        self.groupBox_2.setGeometry(QtCore.QRect(0, 160, 541, 121))
        self.groupBox_2.setObjectName("groupBox_2")
        self.textEdit_2 = QtWidgets.QTextEdit(self.groupBox_2)
        self.textEdit_2.setGeometry(QtCore.QRect(10, 20, 521, 91))
        self.textEdit_2.setObjectName("textEdit_2")
        self.pushButton = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton.setGeometry(QtCore.QRect(150, 290, 91, 31))
        self.pushButton.setObjectName("pushButton")
        self.pushButton_2 = QtWidgets.QPushButton(self.groupBox_3)
        self.pushButton_2.setGeometry(QtCore.QRect(280, 290, 91, 31))
        self.pushButton_2.setObjectName("pushButton_2")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 583, 23))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        QtCore.QMetaObject.connectSlotsByName(MainWindow)

        # 设置窗口信息
        MainWindow.setWindowTitle("Sha1加密")
        self.groupBox_3.setTitle("Sha1加密")
        self.groupBox.setTitle("待输出数据")
        self.groupBox_2.setTitle("结果")
        self.pushButton.setText("加密")
        self.pushButton_2.setText("清空")
        # 设置不可输入
        self.textEdit_2.setReadOnly(True)
        self.textEdit.setAcceptRichText(False)

        # 点击按钮
        self.pushButton.clicked.connect(self.set_sha1_value)
        self.pushButton_2.clicked.connect(self._clear)

    def sha1_encryption(self):
        """参数sha1加密"""
        enc_data = sha1()
        params = self.textEdit.toPlainText()
        enc_data.update(params.encode(encoding="utf-8"))
        return enc_data.hexdigest()

    def set_sha1_value(self):
        sha_data = self.sha1_encryption()
        self.textEdit_2.setText(str(sha_data))

    def _clear(self):
        self.textEdit.clear()
        self.textEdit_2.clear()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    app.setStyleSheet(stylesheet)
    ui = NewTestCaseTools()
    ui.show()
    sys.exit(app.exec_())
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.
  • 363.
  • 364.
  • 365.
  • 366.
  • 367.
  • 368.
  • 369.
  • 370.
  • 371.
  • 372.
  • 373.
  • 374.
  • 375.
  • 376.
  • 377.
  • 378.
  • 379.
  • 380.
  • 381.
  • 382.
  • 383.
  • 384.
  • 385.
  • 386.
  • 387.
  • 388.
  • 389.
  • 390.
  • 391.
  • 392.
  • 393.
  • 394.
  • 395.
  • 396.
  • 397.
  • 398.
  • 399.
  • 400.
  • 401.
  • 402.
  • 403.
  • 404.
  • 405.
  • 406.
  • 407.
  • 408.
  • 409.
  • 410.
  • 411.
  • 412.
  • 413.
  • 414.
  • 415.
  • 416.
  • 417.
  • 418.
  • 419.
  • 420.
  • 421.
  • 422.
  • 423.
  • 424.
  • 425.
  • 426.
  • 427.
  • 428.
  • 429.
  • 430.
  • 431.
  • 432.
  • 433.
  • 434.
  • 435.
  • 436.
  • 437.
  • 438.
  • 439.
  • 440.
  • 441.
  • 442.
  • 443.
  • 444.
  • 445.
  • 446.
  • 447.
  • 448.
  • 449.
  • 450.
  • 451.
  • 452.
  • 453.
  • 454.
  • 455.
  • 456.
  • 457.
  • 458.
  • 459.
  • 460.
  • 461.
  • 462.
  • 463.
  • 464.
  • 465.
  • 466.
  • 467.
  • 468.
  • 469.
  • 470.
  • 471.
  • 472.
  • 473.
  • 474.
  • 475.
  • 476.
  • 477.
  • 478.
  • 479.
  • 480.
  • 481.
  • 482.
  • 483.
  • 484.
  • 485.
  • 486.
  • 487.
  • 488.
  • 489.
  • 490.
  • 491.
  • 492.
  • 493.
  • 494.
  • 495.
  • 496.
  • 497.
  • 498.
  • 499.
  • 500.
  • 501.
  • 502.
  • 503.
  • 504.
  • 505.
  • 506.
  • 507.
  • 508.
  • 509.
  • 510.
  • 511.
  • 512.
  • 513.
  • 514.
  • 515.
  • 516.
  • 517.
  • 518.
  • 519.
  • 520.
  • 521.
  • 522.
  • 523.
  • 524.
  • 525.
  • 526.
  • 527.
  • 528.
  • 529.
  • 530.
  • 531.
  • 532.
  • 533.
  • 534.
  • 535.
  • 536.
  • 537.
  • 538.
  • 539.
  • 540.
  • 541.
  • 542.
  • 543.
  • 544.
  • 545.
  • 546.
  • 547.
  • 548.
  • 549.
  • 550.
  • 551.
  • 552.
  • 553.
  • 554.
  • 555.
  • 556.
  • 557.
  • 558.
  • 559.
  • 560.
  • 561.
  • 562.
  • 563.
  • 564.
  • 565.
  • 566.
  • 567.
  • 568.
  • 569.
  • 570.
  • 571.
  • 572.
  • 573.
  • 574.
  • 575.
  • 576.
  • 577.
  • 578.
  • 579.
  • 580.
  • 581.
  • 582.
  • 583.
  • 584.
  • 585.
  • 586.
  • 587.
  • 588.
  • 589.
  • 590.
  • 591.
  • 592.
  • 593.
  • 594.
  • 595.
  • 596.
  • 597.
  • 598.
  • 599.
  • 600.
  • 601.
  • 602.
  • 603.
  • 604.
  • 605.
  • 606.
  • 607.
  • 608.
  • 609.
  • 610.
  • 611.
  • 612.
  • 613.
  • 614.
  • 615.
  • 616.
  • 617.
  • 618.
  • 619.
  • 620.
  • 621.
  • 622.
  • 623.
  • 624.
  • 625.
  • 626.
  • 627.
  • 628.
  • 629.
  • 630.
  • 631.
  • 632.
  • 633.
  • 634.
  • 635.
  • 636.
  • 637.
  • 638.
  • 639.
  • 640.
  • 641.
  • 642.
  • 643.
  • 644.
  • 645.
  • 646.
  • 647.
  • 648.
  • 649.
  • 650.
  • 651.
  • 652.
  • 653.
  • 654.
  • 655.
  • 656.
  • 657.
  • 658.
  • 659.
  • 660.
  • 661.
  • 662.
  • 663.
  • 664.
  • 665.
  • 666.
  • 667.
  • 668.
  • 669.
  • 670.
  • 671.
  • 672.
  • 673.
  • 674.
  • 675.
  • 676.
  • 677.
  • 678.
  • 679.
  • 680.
  • 681.
  • 682.
  • 683.
  • 684.
  • 685.
  • 686.
  • 687.
  • 688.
  • 689.
  • 690.
  • 691.
  • 692.
  • 693.
  • 694.
  • 695.
  • 696.
  • 697.
  • 698.
  • 699.
  • 700.
  • 701.
  • 702.
  • 703.
  • 704.
  • 705.
  • 706.
  • 707.
  • 708.
  • 709.
  • 710.
  • 711.
  • 712.
  • 713.
  • 714.
  • 715.
  • 716.
  • 717.
  • 718.
  • 719.
  • 720.
  • 721.
  • 722.
  • 723.
  • 724.
  • 725.
  • 726.
  • 727.
  • 728.
  • 729.
  • 730.
  • 731.
  • 732.
  • 733.
  • 734.
  • 735.
  • 736.
  • 737.
  • 738.
  • 739.
  • 740.
  • 741.
  • 742.
  • 743.
  • 744.
  • 745.
  • 746.
  • 747.
  • 748.
  • 749.
  • 750.
  • 751.
  • 752.
  • 753.
  • 754.
  • 755.
  • 756.
  • 757.
  • 758.
  • 759.
  • 760.
  • 761.
  • 762.
  • 763.
  • 764.
  • 765.
  • 766.
  • 767.
  • 768.
  • 769.
  • 770.
  • 771.
  • 772.
  • 773.
  • 774.
  • 775.
  • 776.
  • 777.
  • 778.
  • 779.
  • 780.
  • 781.
  • 782.
  • 783.
  • 784.
  • 785.
  • 786.
  • 787.
  • 788.
  • 789.
  • 790.
  • 791.
  • 792.
  • 793.
  • 794.
  • 795.
  • 796.
  • 797.
  • 798.
  • 799.
  • 800.
  • 801.
  • 802.
  • 803.
  • 804.
  • 805.
  • 806.
  • 807.
  • 808.
  • 809.
  • 810.
  • 811.
  • 812.
  • 813.
  • 814.
  • 815.
  • 816.
  • 817.
  • 818.
  • 819.
  • 820.
  • 821.
  • 822.
  • 823.
  • 824.
  • 825.
  • 826.
  • 827.
  • 828.
  • 829.
  • 830.
  • 831.
  • 832.
  • 833.
  • 834.
  • 835.
  • 836.
  • 837.
  • 838.
  • 839.
  • 840.
  • 841.
  • 842.
  • 843.
  • 844.
  • 845.
  • 846.
  • 847.
  • 848.
  • 849.
  • 850.
  • 851.
  • 852.
  • 853.
  • 854.
  • 855.
  • 856.
  • 857.
  • 858.
  • 859.
  • 860.
  • 861.
  • 862.
  • 863.
  • 864.
  • 865.
  • 866.
  • 867.
  • 868.
  • 869.
  • 870.
  • 871.
  • 872.
  • 873.
  • 874.
  • 875.
  • 876.
  • 877.
  • 878.
  • 879.
  • 880.
  • 881.
  • 882.
  • 883.
  • 884.
  • 885.
  • 886.
  • 887.
  • 888.
  • 889.
  • 890.
  • 891.
  • 892.
  • 893.
  • 894.
  • 895.
  • 896.
  • 897.
  • 898.
  • 899.
  • 900.
  • 901.
  • 902.
  • 903.
  • 904.
  • 905.
  • 906.
  • 907.
  • 908.
  • 909.
  • 910.
  • 911.
  • 912.
  • 913.
  • 914.
  • 915.
  • 916.
  • 917.
  • 918.
  • 919.
  • 920.
  • 921.
  • 922.
  • 923.
  • 924.
  • 925.
  • 926.
  • 927.
  • 928.
  • 929.
  • 930.
  • 931.
  • 932.
  • 933.
  • 934.
  • 935.
  • 936.
  • 937.
  • 938.
  • 939.
  • 940.
  • 941.
  • 942.
  • 943.
  • 944.
  • 945.
  • 946.
  • 947.
  • 948.
  • 949.
  • 950.
  • 951.
  • 952.
  • 953.
  • 954.
  • 955.
  • 956.
  • 957.
  • 958.
  • 959.
  • 960.
  • 961.
  • 962.
  • 963.
  • 964.
  • 965.
  • 966.
  • 967.
  • 968.
  • 969.
  • 970.
  • 971.
  • 972.
  • 973.
  • 974.
  • 975.
  • 976.
  • 977.
  • 978.
  • 979.
  • 980.

base下的remove_file.py

import os
from common.recordlog import logs


def remove_file(filepath, endlst):
    """
    删除文件
    :param filepath: 路径
    :param endlst: 删除的后缀,例如:['json','txt','attach']
    :return:
    """
    try:
        if os.path.exists(filepath):
            # 获取该目录下所有文件名称
            dir_lst_files = os.listdir(filepath)
            for file_name in dir_lst_files:
                fpath = filepath + '\\' + file_name
                # endswith判断字符串是否以指定后缀结尾
                if isinstance(endlst, list):
                    for ft in endlst:
                        if file_name.endswith(ft):
                            os.remove(fpath)
                else:
                    raise TypeError('file Type error,must is list')
        else:
            os.makedirs(filepath)
    except Exception as e:
        logs.error(e)


def remove_directory(path):
    try:
        if os.path.exists(path):
            os.remove(path)
    except Exception as e:
        logs.error(e)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.

common下的assertions:

import traceback
import allure
import jsonpath
import operator

from common.recordlog import logs
from common.connection import ConnectMysql


class Assertions:
    """"
    接口断言模式,支持
    1)响应文本字符串包含模式断言
    2)响应结果相等断言
    3)响应结果不相等断言
    4)响应结果任意值断言
    5)数据库断言

    """

    def contains_assert(self, value, response, status_code):
        """
        字符串包含断言模式,断言预期结果的字符串是否包含在接口的响应信息中
        :param value: 预期结果,yaml文件的预期结果值
        :param response: 接口实际响应结果
        :param status_code: 响应状态码
        :return: 返回结果的状态标识
        """
        # 断言状态标识,0成功,其他失败
        flag = 0
        for assert_key, assert_value in value.items():
            if assert_key == "status_code":
                if assert_value != status_code:
                    flag += 1
                    allure.attach(f"预期结果:{assert_value}\n实际结果:{status_code}", '响应代码断言结果:失败',
                                  attachment_type=allure.attachment_type.TEXT)
                    logs.error("contains断言失败:接口返回码【%s】不等于【%s】" % (status_code, assert_value))
            else:
                resp_list = jsonpath.jsonpath(response, "$..%s" % assert_key)
                if isinstance(resp_list[0], str):
                    resp_list = ''.join(resp_list)
                if resp_list:
                    assert_value = None if assert_value.upper() == 'NONE' else assert_value
                    if assert_value in resp_list:
                        logs.info("字符串包含断言成功:预期结果【%s】,实际结果【%s】" % (assert_value, resp_list))
                    else:
                        flag = flag + 1
                        allure.attach(f"预期结果:{assert_value}\n实际结果:{resp_list}", '响应文本断言结果:失败',
                                      attachment_type=allure.attachment_type.TEXT)
                        logs.error("响应文本断言失败:预期结果为【%s】,实际结果为【%s】" % (assert_value, resp_list))
        return flag

    def equal_assert(self, expected_results, actual_results, statuc_code=None):
        """
        相等断言模式
        :param expected_results: 预期结果,yaml文件validation值
        :param actual_results: 接口实际响应结果
        :return:
        """
        flag = 0
        if isinstance(actual_results, dict) and isinstance(expected_results, dict):
            # 找出实际结果与预期结果共同的key
            common_keys = list(expected_results.keys() & actual_results.keys())[0]
            # 根据相同的key去实际结果中获取,并重新生成一个实际结果的字典
            new_actual_results = {common_keys: actual_results[common_keys]}
            eq_assert = operator.eq(new_actual_results, expected_results)
            if eq_assert:
                logs.info(f"相等断言成功:接口实际结果:{new_actual_results},等于预期结果:" + str(expected_results))
                allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '相等断言结果:成功',
                              attachment_type=allure.attachment_type.TEXT)
            else:
                flag += 1
                logs.error(f"相等断言失败:接口实际结果{new_actual_results},不等于预期结果:" + str(expected_results))
                allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '相等断言结果:失败',
                              attachment_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('相等断言--类型错误,预期结果和接口实际响应结果必须为字典类型!')
        return flag

    def not_equal_assert(self, expected_results, actual_results, statuc_code=None):
        """
        不相等断言模式
        :param expected_results: 预期结果,yaml文件validation值
        :param actual_results: 接口实际响应结果
        :return:
        """
        flag = 0
        if isinstance(actual_results, dict) and isinstance(expected_results, dict):
            # 找出实际结果与预期结果共同的key
            common_keys = list(expected_results.keys() & actual_results.keys())[0]
            # 根据相同的key去实际结果中获取,并重新生成一个实际结果的字典
            new_actual_results = {common_keys: actual_results[common_keys]}
            eq_assert = operator.ne(new_actual_results, expected_results)
            if eq_assert:
                logs.info(f"不相等断言成功:接口实际结果:{new_actual_results},不等于预期结果:" + str(expected_results))
                allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '不相等断言结果:成功',
                              attachment_type=allure.attachment_type.TEXT)
            else:
                flag += 1
                logs.error(f"不相等断言失败:接口实际结果{new_actual_results},等于预期结果:" + str(expected_results))
                allure.attach(f"预期结果:{str(expected_results)}\n实际结果:{new_actual_results}", '不相等断言结果:失败',
                              attachment_type=allure.attachment_type.TEXT)
        else:
            raise TypeError('不相等断言--类型错误,预期结果和接口实际响应结果必须为字典类型!')
        return flag

    def assert_response_any(self, actual_results, expected_results):
        """
        断言接口响应信息中的body的任何属性值
        :param actual_results: 接口实际响应信息
        :param expected_results: 预期结果,在接口返回值的任意值
        :return: 返回标识,0表示测试通过,非0则测试失败
        """
        flag = 0
        try:
            exp_key = list(expected_results.keys())[0]
            if exp_key in actual_results:
                act_value = actual_results[exp_key]
                rv_assert = operator.eq(act_value, list(expected_results.values())[0])
                if rv_assert:
                    logs.info("响应结果任意值断言成功")
                else:
                    flag += 1
                    logs.error("响应结果任意值断言失败")
        except Exception as e:
            logs.error(e)
            raise
        return flag

    def assert_response_time(self, res_time, exp_time):
        """
        通过断言接口的响应时间与期望时间对比,接口响应时间小于预期时间则为通过
        :param res_time: 接口的响应时间
        :param exp_time: 预期的响应时间
        :return:
        """
        try:
            assert res_time < exp_time
            return True
        except Exception as e:
            logs.error('接口响应时间[%ss]大于预期时间[%ss]' % (res_time, exp_time))
            raise

    def assert_mysql_data(self, expected_results):
        """
        数据库断言
        :param expected_results: 预期结果,yaml文件的SQL语句
        :return: 返回flag标识,0表示正常,非0表示测试不通过
        """
        flag = 0
        conn = ConnectMysql()
        db_value = conn.query_all(expected_results)
        if db_value is not None:
            logs.info("数据库断言成功")
        else:
            flag += 1
            logs.error("数据库断言失败,请检查数据库是否存在该数据!")
        return flag

    def assert_result(self, expected, response, status_code):
        """
        断言,通过断言all_flag标记,all_flag==0表示测试通过,否则为失败
        :param expected: 预期结果
        :param response: 实际响应结果
        :param status_code: 响应code码
        :return:
        """
        all_flag = 0
        try:
            logs.info("yaml文件预期结果:%s" % expected)
            # logs.info("实际结果:%s" % response)
            # all_flag = 0
            for yq in expected:
                for key, value in yq.items():
                    if key == "contains":
                        flag = self.contains_assert(value, response, status_code)
                        all_flag = all_flag + flag
                    elif key == "eq":
                        flag = self.equal_assert(value, response)
                        all_flag = all_flag + flag
                    elif key == 'ne':
                        flag = self.not_equal_assert(value, response)
                        all_flag = all_flag + flag
                    elif key == 'rv':
                        flag = self.assert_response_any(actual_results=response, expected_results=value)
                        all_flag = all_flag + flag
                    elif key == 'db':
                        flag = self.assert_mysql_data(value)
                        all_flag = all_flag + flag
                    else:
                        logs.error("不支持此种断言方式")

        except Exception as exceptions:
            logs.error('接口断言异常,请检查yaml预期结果值是否正确填写!')
            raise exceptions

        if all_flag == 0:
            logs.info("测试成功")
            assert True
        else:
            logs.error("测试失败")
            assert False
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.

 common下的conection.py:

import traceback

import clickhouse_sqlalchemy
import pymysql
import redis
import sys
import pymongo
import paramiko
import pandas as pd
from clickhouse_sqlalchemy import make_session, exceptions
from sqlalchemy import create_engine
from conf.operationConfig import OperationConfig
from common.recordlog import logs
from common.two_dimension_data import print_table

conf = OperationConfig()


class ConnectMysql:

    def __init__(self):

        mysql_conf = {
            'host': conf.get_section_mysql('host'),
            'port': int(conf.get_section_mysql('port')),
            'user': conf.get_section_mysql('username'),
            'password': conf.get_section_mysql('password'),
            'database': conf.get_section_mysql('database')
        }

        try:
            self.conn = pymysql.connect(**mysql_conf, charset='utf8')
            # cursor=pymysql.cursors.DictCursor,将数据库表字段显示,以key-value形式展示
            self.cursor = self.conn.cursor(cursor=pymysql.cursors.DictCursor)
            logs.info("""成功连接到mysql---
            host:{host}
            port:{port}
            db:{database}
            """.format(**mysql_conf))
        except Exception as e:
            logs.error(f"except:{e}")

    def close(self):
        if self.conn and self.cursor:
            self.cursor.close()
            self.conn.close()
        return True

    def query_all(self, sql):
        try:
            self.cursor.execute(sql)
            self.conn.commit()
            res = self.cursor.fetchall()

            keys = ''
            values = []
            for item in res:
                keys = list(item.keys())

            for ite in res:
                values.append(list(ite.values()))

            for val in values:
                # lst_format = [
                #     keys,
                #     val
                # ]
                lst_format = [
                    val
                ]

                return lst_format
                # return print_table(lst_format)

        except Exception as e:
            logs.error(e)
        finally:
            self.close()

    def delete(self, sql):
        try:
            self.cursor.execute(sql)
            self.conn.commit()
            logs.info('删除成功')
        except Exception as e:
            logs.error(e)
        finally:
            self.close()


class ConnectRedis:

    def __init__(self, ip=conf.get_section_redis("host"), port=conf.get_section_redis("port"), username=None,
                 passwd=None, db=conf.get_section_redis("db")):
        self.host = ip
        self.port = port
        self.username = username
        self.password = passwd
        self.db = db
        # 使用连接池方式,decode_responses=True可自动转为字符串
        logs.info(f"连接Redis--host:{ip},port:{port},user:{username},password:{passwd},db:{db}")
        try:
            pool = redis.ConnectionPool(host=self.host, port=int(self.port), password=self.password)
            self.first_conn = redis.Redis(connection_pool=pool, decode_responses=True)
            # print(self.first_conn.keys())
        except Exception:
            logs.error(str(traceback.format_exc()))

    def set_kv(self, key, value, ex=None):
        """
        :param key:
        :param value:
        :param ex: 过期时间,秒
        :return:
        """
        try:
            return self.first_conn.set(name=key, value=value, ex=ex)
        except Exception:
            logs.error(str(traceback.format_exc()))

    def get_kv(self, name):
        try:
            return self.first_conn.get(name)
        except Exception:
            logs.error(str(traceback.format_exc()))

    def hash_set(self, key, value, ex=None):
        try:
            return self.first_conn.set(name=key, value=value, ex=ex)
        except Exception:
            logs.error(str(traceback.format_exc()))

    def hash_hget(self, names, keys):
        """在name对应的hash中获取根据key获取value"""
        try:
            data = self.first_conn.hget(names, keys).decode()
            return data
        except Exception:
            logs.error(str(traceback.format_exc()))

    def hash_hmget(self, name, keys, *args):
        """在name对应的hash中获取多个key的值"""
        if not isinstance(keys, list):
            raise ("keys应为列表")
        try:
            return self.first_conn.hmget(name, keys, *args)
        except Exception:
            logs.error(str(traceback.format_exc()))


class ConnectClickHouse:
    """
    clickhouse有两个端口,8123和9000,分别用于接收 http协议和tcp协议请求,管理后台登录用的8123(jdbc连接),
    而程序连接clickhouse(driver连接)则需要使用9000端口。如果在程序中使用8123端口连接就会报错
    """

    def __init__(self):

        config = {
            'server_host': conf.get_section_clickhouse('host'),
            'port': conf.get_section_clickhouse('port'),
            'user': conf.get_section_clickhouse('username'),
            'password': conf.get_section_clickhouse('password'),
            'db': conf.get_section_clickhouse('db'),
            'send_receive_timeout': conf.get_section_clickhouse('timeout')
        }
        try:
            connection = 'clickhouse://{user}:{password}@{server_host}:{port}/{db}'.format(**config)
            engine = create_engine(connection, pool_size=100, pool_recycle=3600, pool_timeout=20)
            self.session = make_session(engine)
            logs.info("""成功连接到clickhouse--
            server_host:{server_host}
            port:{port}
            database:{db}
            timeout:{send_receive_timeout}
            """.strip().format(**config))
        except Exception:
            logs.error(str(traceback.format_exc()))

    def sql(self, sql):
        """
        :param sql: sql语句后面不要带分号;,有时带上会报错
        :return:
        """
        cursor = self.session.execute(sql)
        try:
            fields = cursor._metadata.keys
            df = pd.DataFrame([dict(zip(fields, item)) for item in cursor.fetchall()])
            return df
        except clickhouse_sqlalchemy.exceptions.DatabaseException:
            logs.error('SQL语法错误,请检查SQL语句')
        except Exception as e:
            print(e)
        finally:
            cursor.close()
            self.session.close()


class ConnectMongo(object):

    def __init__(self):

        mg_conf = {
            'host': conf.get_section_mongodb("host"),
            'port': int(conf.get_section_mongodb("port")),
            'user': conf.get_section_mongodb("username"),
            'passwd': conf.get_section_mongodb("password"),
            'db': conf.get_section_mongodb("database")
        }

        try:
            client = pymongo.MongoClient(
                'mongodb://{user}:{passwd}@{host}:{port}/{db}'.format(**mg_conf))
            self.db = client[mg_conf['db']]
            logs.info("连接到MongoDB,ip:{host},port:{port},database:{db}".format(**mg_conf))
        except Exception as e:
            logs.error(e)

    def use_collection(self, collection):
        try:
            collect_table = self.db[collection]
        except Exception as e:
            logs.error(e)
        else:
            return collect_table

    def insert_one_data(self, data, collection):
        """
        :param data: 插入的数据
        :param collection: 插入集合
        :return:
        """
        try:
            self.use_collection(collection).insert_one(data)
        except Exception as e:
            logs.error(e)

    def insert_many_data(self, documents, collection):
        """
        :param args: 插入多条数据
        :param collection:
        :return:
        """
        if not isinstance(documents, list):
            raise TypeError("参数必须是一个非空的列表")
        for item in documents:
            try:
                self.use_collection(collection).insert_many([item])
            except Exception as e:
                logs.error(e)
                return None

    def query_one_data(self, query_parame, collection):
        """
        查询一条数据
        :param query_parame: 查询参数,dict类型,如:{'entId':'2192087652225949165'}
        :param collection: Mongo集合,数据存放路径,集合存储在database,集合类似mysql的表
        :return:
        """
        if not isinstance(query_parame, dict):
            raise TypeError("查询参数必须为dict类型")
        try:
            res = self.use_collection(collection=collection).find_one(query_parame)
            return res
        except Exception as e:
            logs.error(e)

    def query_all_data(self, collection, query_parame=None, limit_num=sys.maxsize):
        """
        查询多条数据
        :param collection: Mongo集合,数据存放路径,集合存储在database,集合类似mysql的表
        :param query_parame: 查询参数,dict类型,如:{'entId':'2192087652225949165'}
        :param limit_num: 查询数量限制
        :return:
        """

        table = self.use_collection(collection)
        if query_parame is not None:
            if not isinstance(query_parame, dict):
                raise TypeError("查询参数必须为dict类型")
        try:
            query_results = table.find(query_parame).limit(limit_num)  # limit限制结果集查询数量
            res_list = [res for res in query_results]
            return res_list
        except Exception:
            return None

    def update_collection(self, query_conditions, after_change, collection):
        """
        :param query_conditions: 目标参数
        :param after_change: 需要更改的数据
        """
        if not isinstance(query_conditions, dict) or not isinstance(after_change, dict):
            raise TypeError("参数必须为dict类型")
        res = self.query_one_data(query_conditions, collection)
        if res is not None:
            try:
                self.use_collection(collection).update_one(query_conditions, {"$set": after_change})
            except Exception as e:
                logs.error(e)
                return None
        else:
            logs.info("查询条件不存在")

    def delete_collection(self, search, collection):
        """删除一条数据"""
        if not isinstance(search, dict):
            raise TypeError("参数必须为dict类型")
        try:
            self.use_collection(collection).delete_one(search)
        except Exception as e:
            logs.error(e)

    def delete_many_collection(self, search, collecton):
        try:
            self.use_collection(collecton).delete_many(search)
        except Exception:
            return None

    def drop_collection(self, collection):
        """删除集合"""
        try:
            self.use_collection(collection).drop()
            logs.info("delete success")
        except Exception:
            return None


class ConnectSSH(object):
    """连接SSH终端服务"""

    def __init__(self,
                 host=None,
                 port=22,
                 username=None,
                 password=None,
                 timeout=None):
        self.__conn_info = {
            'hostname': conf.get_section_ssh('host') if host is None else host,
            'port': int(conf.get_section_ssh('port')) if port is not None else port,
            'username': conf.get_section_ssh('username') if username is None else username,
            'password': conf.get_section_ssh('password') if password is None else password,
            'timeout': int(conf.get_section_ssh('timeout')) if timeout is None else timeout
        }

        self.__client = paramiko.SSHClient()
        self.__client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.__client.connect(**self.__conn_info)

        if self.__client:
            logs.info('{}服务端连接成功'.format(self.__conn_info['hostname']))

    def get_ssh_content(self, command=None):
        stdin, stdout, stderr = self.__client.exec_command(
            command if command is not None else conf.get_section_ssh('command'))
        content = stdout.read().decode()
        return content


class ConnectOracle:
    def __init__(self):
        pass
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.
  • 195.
  • 196.
  • 197.
  • 198.
  • 199.
  • 200.
  • 201.
  • 202.
  • 203.
  • 204.
  • 205.
  • 206.
  • 207.
  • 208.
  • 209.
  • 210.
  • 211.
  • 212.
  • 213.
  • 214.
  • 215.
  • 216.
  • 217.
  • 218.
  • 219.
  • 220.
  • 221.
  • 222.
  • 223.
  • 224.
  • 225.
  • 226.
  • 227.
  • 228.
  • 229.
  • 230.
  • 231.
  • 232.
  • 233.
  • 234.
  • 235.
  • 236.
  • 237.
  • 238.
  • 239.
  • 240.
  • 241.
  • 242.
  • 243.
  • 244.
  • 245.
  • 246.
  • 247.
  • 248.
  • 249.
  • 250.
  • 251.
  • 252.
  • 253.
  • 254.
  • 255.
  • 256.
  • 257.
  • 258.
  • 259.
  • 260.
  • 261.
  • 262.
  • 263.
  • 264.
  • 265.
  • 266.
  • 267.
  • 268.
  • 269.
  • 270.
  • 271.
  • 272.
  • 273.
  • 274.
  • 275.
  • 276.
  • 277.
  • 278.
  • 279.
  • 280.
  • 281.
  • 282.
  • 283.
  • 284.
  • 285.
  • 286.
  • 287.
  • 288.
  • 289.
  • 290.
  • 291.
  • 292.
  • 293.
  • 294.
  • 295.
  • 296.
  • 297.
  • 298.
  • 299.
  • 300.
  • 301.
  • 302.
  • 303.
  • 304.
  • 305.
  • 306.
  • 307.
  • 308.
  • 309.
  • 310.
  • 311.
  • 312.
  • 313.
  • 314.
  • 315.
  • 316.
  • 317.
  • 318.
  • 319.
  • 320.
  • 321.
  • 322.
  • 323.
  • 324.
  • 325.
  • 326.
  • 327.
  • 328.
  • 329.
  • 330.
  • 331.
  • 332.
  • 333.
  • 334.
  • 335.
  • 336.
  • 337.
  • 338.
  • 339.
  • 340.
  • 341.
  • 342.
  • 343.
  • 344.
  • 345.
  • 346.
  • 347.
  • 348.
  • 349.
  • 350.
  • 351.
  • 352.
  • 353.
  • 354.
  • 355.
  • 356.
  • 357.
  • 358.
  • 359.
  • 360.
  • 361.
  • 362.

 common下的debugtalk.py:

import base64
import calendar
import datetime
import hashlib
import os.path
import random
import re
import time
from hashlib import sha1
from conf.setting import DIR_BASE
from pandas.tseries.offsets import Day
from common.operationcsv import read_csv
from common.readyaml import ReadYamlData
import csv


class DebugTalk:

    def __init__(self):
        self.read = ReadYamlData()

    def get_extract_data(self, node_name, randoms=None) -> str:
        """
        获取extract.yaml数据,首先判断randoms是否为数字类型,如果不是就获取下一个node节点的数据
        :param node_name: extract.yaml文件中的key值
        :param randoms: int类型,0:随机读取;-1:读取全部,返回字符串形式;-2:读取全部,返回列表形式;其他根据列表索引取值,取第一个值为1,第二个为2,以此类推;
        :return:
        """
        data = self.read.get_extract_yaml(node_name)
        if randoms is not None and bool(re.compile(r'^[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?$').match(randoms)):
            randoms = int(randoms)
            data_value = {
                randoms: self.get_extract_order_data(data, randoms),
                0: random.choice(data),
                -1: ','.join(data),
                -2: ','.join(data).split(','),
            }
            data = data_value[randoms]
        else:
            data = self.read.get_extract_yaml(node_name, randoms)
        return data

    def get_extract_order_data(self, data, randoms):
        """获取extract.yaml数据,不为0、-1、-2,则按顺序读取文件key的数据"""
        if randoms not in [0, -1, -2]:
            return data[randoms - 1]

    def md5_encryption(self, params):
        """参数MD5加密"""
        enc_data = hashlib.md5()
        enc_data.update(params.encode(encoding="utf-8"))
        return enc_data.hexdigest()

    def sha1_encryption(self, params):
        """参数sha1加密"""
        enc_data = sha1()
        enc_data.update(params.encode(encoding="utf-8"))
        return enc_data.hexdigest()

    def base64_encryption(self, params):
        """base64加密"""
        base_params = params.encode("utf-8")
        encr = base64.b64encode(base_params)
        return encr

    def timestamp(self):
        """获取当前时间戳,10位"""
        t = int(time.time())
        return t

    def timestamp_thirteen(self):
        """获取当前的时间戳,13位"""
        t = int(time.time()) * 1000
        return t

    def start_time(self):
        """获取当前时间的前一天标准时间"""
        now_time = datetime.datetime.now()
        day_before_time = (now_time - 1 * Day()).strftime("%Y-%m-%d %H:%M:%S")
        return day_before_time

    def end_time(self):
        """获取当前时间标准时间格式"""
        now_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        return now_time

    def start_forward_time(self):
        """获取当前时间的前15天标准时间,年月日"""
        now_time = datetime.datetime.now()
        day_before_time = (now_time - 15 * Day()).strftime("%Y-%m-%d")
        return day_before_time

    def start_after_time(self):
        """获取当前时间的后7天标准时间,年月日"""
        now_time = datetime.datetime.now()
        day_after_time = (now_time + 7 * Day()).strftime("%Y-%m-%d")
        return day_after_time

    def end_year_time(self):
        """获取当前时间标准时间格式,年月日"""
        now_time = datetime.datetime.now().strftime("%Y-%m-%d")
        return now_time

    def today_zero_tenstamp(self):
        """获取当天00:00:00时间戳,10位时间戳"""
        time_stamp = int(time.mktime(datetime.date.today().timetuple()))
        return time_stamp

    def today_zero_stamp(self):
        """获取当天00:00:00时间戳,13位时间戳"""
        time_stamp = int(time.mktime(datetime.date.today().timetuple())) * 1000
        return time_stamp

    def specified_zero_tamp(self, days):
        """获取当前日期指定日期的00:00:00时间戳,days:往前为负数,往后为整数"""
        tom = datetime.date.today() + datetime.timedelta(days=int(days))
        date_tamp = int(time.mktime(time.strptime(str(tom), '%Y-%m-%d'))) * 1000
        return date_tamp

    def specified_end_tamp(self, days):
        """获取当前日期指定日期的23:59:59时间戳,days:往前为负数,往后为整数"""
        tom = datetime.date.today() + datetime.timedelta(days=int(days) + 1)
        date_tamp = int(time.mktime(time.strptime(str(tom), '%Y-%m-%d'))) - 1
        return date_tamp * 1000

    def today_end_stamp(self):
        """获取当天23:59:59时间戳"""
        # 今天日期
        today = datetime.date.today()
        # 明天日期
        tomorrow = today + datetime.timedelta(days=1)
        today_end_time = int(time.mktime(time.strptime(str(tomorrow), '%Y-%m-%d'))) - 1
        return today_end_time * 1000

    def month_start_time(self):
        """获取本月第一天标准时间,年月日"""
        # 今天日期
        now = datetime.datetime.now()
        this_month_start = datetime.datetime(now.year, now.month, 1).strftime("%Y-%m-%d")
        return this_month_start

    def month_end_time(self):
        """获取本月最后一天标准时间,年月日"""
        # 今天日期
        now = datetime.datetime.now()
        this_month_end = datetime.datetime(now.year, now.month, calendar.monthrange(now.year, now.month)[1]).strftime(
            "%Y-%m-%d")
        return this_month_end

    def month_first_time(self):
        """本月1号00:00:00时间戳,13位"""
        # 今天日期
        now = datetime.datetime.now()
        # 本月第一天日期
        this_month_start = datetime.datetime(now.year, now.month, 1)
        first_time_stamp = int(time.mktime(this_month_start.timetuple())) * 1000
        return first_time_stamp

    def fenceAlarm_alarmType_random(self):
        alarm_type = ["1", "3", "8", "2", "5", "6"]
        fence_alarm = random.choice(alarm_type)
        return fence_alarm

    def fatigueAlarm_alarmType_random(self):
        alarm_type = ["1", "3", "8"]
        fatigue_alarm = random.choice(alarm_type)
        return fatigue_alarm

    def jurisdictionAlarm_random(self):
        alarm_type = ["1", "3", "8", "2", "5", "6", "9"]
        jurisdiction_alarm = random.choice(alarm_type)
        return jurisdiction_alarm

    def vehicle_random(self):
        """从csv中随机读取车牌号"""
        data = read_csv(os.path.join(DIR_BASE, 'data', 'vehicleNo.csv'), 'vno')
        vel_num = random.choice(data)
        return vel_num

    def read_csv_data(self, file_name, index):
        """读取csv数据,csv文件中不用带字段名,直接写测试数据即可"""
        with open(os.path.join(DIR_BASE, 'data', file_name), 'r', encoding='utf-8') as f:
            csv_reader = list(csv.reader(f))
            user_lst, passwd_lst = [], []
            for user, passwd in csv_reader:
                user_lst.append(user)
                passwd_lst.append(passwd)
            return user_lst[0], passwd_lst[0]

    def get_baseurl(self, host):
        from conf.operationConfig import OperationConfig
        conf = OperationConfig()
        url = conf.get_section_for_data('api_envi', host)
        return url
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.
  • 164.
  • 165.
  • 166.
  • 167.
  • 168.
  • 169.
  • 170.
  • 171.
  • 172.
  • 173.
  • 174.
  • 175.
  • 176.
  • 177.
  • 178.
  • 179.
  • 180.
  • 181.
  • 182.
  • 183.
  • 184.
  • 185.
  • 186.
  • 187.
  • 188.
  • 189.
  • 190.
  • 191.
  • 192.
  • 193.
  • 194.

common下的dingrobot:

import urllib.parse
import requests
import time
import hmac
import hashlib
import base64


def generate_sign():
    """
    签名计算
    把timestamp+"\n"+密钥当做签名字符串,使用HmacSHA256算法计算签名,然后进行Base64 encode,
    最后再把签名参数再进行urlEncode,得到最终的签名(需要使用UTF-8字符集)
    :return: 返回当前时间戳、加密后的签名
    """
    # 当前时间戳
    timestamp = str(round(time.time() * 1000))
    # 钉钉机器人中的加签密钥
    secret = '123'
    secret_enc = secret.encode('utf-8')
    str_to_sign = '{}\n{}'.format(timestamp, secret)
    # 转成byte类型
    str_to_sign_enc = str_to_sign.encode('utf-8')
    hmac_code = hmac.new(secret_enc, str_to_sign_enc, digestmod=hashlib.sha256).digest()
    sign = urllib.parse.quote_plus(base64.b64encode(hmac_code))
    return timestamp, sign


def send_dd_msg(content_str, at_all=True):
    """
    向钉钉机器人推送结果
    :param content_str: 发送的内容
    :param at_all: @全员,默认为True
    :return:
    """
    timestamp_and_sign = generate_sign()
    # url(钉钉机器人Webhook地址) + timestamp + sign
    url = f'https://oapi.dingtalk.com/robot/send?access_token=75d6628cefedc8225695dcde2577f03336f0099cd16d93988a68ad243cf9dd6f×tamp={timestamp_and_sign[0]}&sign={timestamp_and_sign[1]}'
    headers = {'Content-Type': 'application/json;charset=utf-8'}
    data = {
        "msgtype": "text",
        "text": {
            "content": content_str
        },
        "at": {
            "isAtAll": at_all
        },
    }
    res = requests.post(url, json=data, headers=headers)
    return res.text
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.

 common下的handleexcel.py

import os

import xlrd
from conf import setting
from xlutils.copy import copy
from common.recordlog import logs
import xlwt


class OperationExcel(object):
    """
    封装读取/写入Excel文件的数据
    """

    def __init__(self, filename=None):
        try:
            if filename is not None:
                self.__filename = filename
            else:
                self.__filename = setting.FILE_PATH['EXCEL']
            self.__sheet_id = setting.SHEET_ID
        except Exception as e:
            logs.error(e)
        self.__GLOBAL_TABLE = self.xls_obj()
        self.colx = 0

    def xls_obj(self):
        xls_obj = ''
        if os.path.splitext(self.__filename)[-1] != '.xlsx':
            data = xlrd.open_workbook(self.__filename, formatting_info=True)
            xls_obj = data.sheets()[self.__sheet_id]
        else:
            logs.error('Excel文件的格式必须为.xls格式,请重新另存为xls格式!')
            exit()
        return xls_obj

    def get_rows(self):
        """
        获取xls文件总行数
        @return:
        """
        return self.__GLOBAL_TABLE.nrows

    def get_cols(self):
        """
        获取总列数
        :return:
        """
        return self.__GLOBAL_TABLE.ncols

    def get_cell_value(self, row, col):
        """
        获取单元格的值
        :param row: excel行数,索引从0开始,第一行索引是0
        :param col: Excel列数,索引从0开始,第一列索引是0
        :return:
        """
        return self.__GLOBAL_TABLE.cell_value(row, col)

    def settingStyle(self):
        """
        设置样式,该功能暂时未生效
        :return:
        """
        style = xlwt.easyfont("font:bold 1,color red")  # 粗体,红色

    def write_xls_value(self, row, col, value):
        """
        写入数据
        :param row: excel行数
        :param col: Excel列数
        :param value: 写入的值
        :return:
        """
        try:
            init_table = xlrd.open_workbook(self.__filename, formatting_info=True)
            copy_table = copy(init_table)
            sheet = copy_table.get_sheet(self.__filename)
            sheet.write(row, col, value)
            copy_table.save(self.__filename)
        except PermissionError:
            logs.error("请先关闭xls文件")
            exit()

    def get_each_line(self, row):
        """
        获取每一行数据
        :param row: excel行数
        :return: 返回一整行的数据
        """
        try:
            return self.__GLOBAL_TABLE.row_values(row)
        except Exception as exp:
            logs.error(exp)

    def get_each_column(self, col=None):
        """
        获取每一列数据
        :param col: Excel列数
        :return: 返回一整列的数据
        """
        if col is None:
            return self.__GLOBAL_TABLE.col_values(self.colx)
        else:
            return self.__GLOBAL_TABLE.col_values(col)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.

common下的operationcvs:

import pandas as pd
from common.recordlog import logs
import traceback


def read_csv(filepath, col_name):
    """
    :param filepath: csv目录
    :param col_name: 取值的列名
    usecols:需要读取的列,可以是列的位置编号,也可以是列的名称
    error_bad_lines = False  当某行数据有问题时,不报错,直接跳过,处理脏数据时使用
    :return:
    """
    try:
        df = pd.read_csv(filepath, encoding="GBK")
        data = df[col_name].tolist()
        return data
    except Exception:
        logs.error(str(traceback.format_exc()))
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

common下的operxml:

import xml.etree.ElementTree as et
from conf.setting import FILE_PATH
from common.recordlog import logs


class OperXML:

    def read_xml(self, filename, tags, attr_value):
        """
        读取XML文件标签值
        :param filename: xml文件,只需要写文件名即可,传参不需要带路径
        :param tags: 读取哪个xml的标签
        :param attr_value: 标签中的属性,如id、name、class属性
        :return:
        """
        root = ''
        file_path = {
            'file': FILE_PATH['XML'] + '\\' + filename
        }
        try:
            tree = et.parse(file_path['file'])
            # 获取xml根标签
            root = tree.getroot()
        except Exception as e:
            logs.error(e)

        child_text = ''
        # 遍历整个xml文件
        for child in root.iter(tags):

            att = child.attrib

            if ''.join(list(att.values())) == attr_value:
                child_text = child.text.strip()
            if child:
                for i in child:
                    attr = i.attrib
                    if ''.join(list(attr.values())) == attr_value:
                        child_text = i.text.strip()

        return child_text

    def get_attribute_value(self, filename, tags):
        """
        读取标签属性值
        :param filename: 文件路径
        :param tags: xml标签名
        :return: dict格式
        """

        root = ''
        file_path = {'file': FILE_PATH['RESULTXML'] + '\\' + filename}
        try:
            tree = et.parse(file_path['file'])
            # 获取xml根标签
            root = tree.getroot()
        except Exception as e:
            logs.error(e)

        attr = [child.attrib for child in root.iter(tags)][0]

        return attr
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.

common下的pjenkins.py:

import json
import re

import jenkins
from conf.operationConfig import OperationConfig


class PJenkins(object):
    conf = OperationConfig()

    def __init__(self):
        self.__config = {
            'url': self.conf.get_section_jenkins('url'),
            'username': self.conf.get_section_jenkins('username'),
            'password': self.conf.get_section_jenkins('password'),
            'timeout': int(self.conf.get_section_jenkins('timeout'))
        }
        self.__server = jenkins.Jenkins(**self.__config)

        self.job_name = self.conf.get_section_jenkins('job_name')

    def get_job_number(self):
        """读取jenkins job构建号"""
        build_number = self.__server.get_job_info(self.job_name).get('lastBuild').get('number')
        return build_number

    def get_build_job_status(self):
        """读取构建完成的状态"""
        build_num = self.get_job_number()
        job_status = self.__server.get_build_info(self.job_name, build_num).get('result')
        return job_status

    def get_console_log(self):
        """获取控制台日志"""
        console_log = self.__server.get_build_console_output(self.job_name, self.get_job_number())
        return console_log

    def get_job_description(self):
        """返回job描述信息"""
        description = self.__server.get_job_info(self.job_name).get('description')
        url = self.__server.get_job_info(self.job_name).get('url')

        return description, url

    def get_build_report(self):
        """返回第n次构建的测试报告"""
        report = self.__server.get_build_test_report(self.job_name, self.get_job_number())
        return report

    def report_success_or_fail(self):
        """统计测试报告用例成功数、失败数、跳过数以及成功率、失败率"""
        report_info = self.get_build_report()
        pass_count = report_info.get('passCount')
        fail_count = report_info.get('failCount')
        skip_count = report_info.get('skipCount')
        total_count = int(pass_count) + int(fail_count) + int(skip_count)
        duration = int(report_info.get('duration'))
        hour = duration // 3600
        minute = duration % 3600 // 60
        seconds = duration % 3600 % 60
        execute_duration = f'{hour}时{minute}分{seconds}秒'
        content = f'本次测试共执行{total_count}个测试用例,成功:{pass_count}个; 失败:{fail_count}个; 跳过:{skip_count}个; 执行时长:{hour}时{minute}分{seconds}秒'
        # 提取测试报告链接
        console_log = self.get_console_log()
        report_line = re.search(r'http://192.168.105.36:8088/job/hbjjapi/(.*?)allure', console_log).group(0)
        report_info = {
            'total': total_count,
            'pass_count': pass_count,
            'fail_count': fail_count,
            'skip_count': skip_count,
            'execute_duration': execute_duration,
            'report_line': report_line
        }
        return report_info


if __name__ == '__main__':
    p = PJenkins()
    res = p.report_success_or_fail()
    # result = re.search(r'http://192.168.105.36:8088/job/hbjjapi/(.*?)allure', res).group(0)
    print(res)
    # print(result)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.

common下的readyyaml:

import yaml
import traceback
import os

from common.recordlog import logs
from conf.operationConfig import OperationConfig
from conf.setting import FILE_PATH
from yaml.scanner import ScannerError


def get_testcase_yaml(file):
    testcase_list = []
    try:
        with open(file, 'r', encoding='utf-8') as f:
            data = yaml.safe_load(f)
            if len(data) <= 1:
                yam_data = data[0]
                base_info = yam_data.get('baseInfo')
                for ts in yam_data.get('testCase'):
                    param = [base_info, ts]
                    testcase_list.append(param)
                return testcase_list
            else:
                return data
    except UnicodeDecodeError:
        logs.error(f"[{file}]文件编码格式错误,--尝试使用utf-8编码解码YAML文件时发生了错误,请确保你的yaml文件是UTF-8格式!")
    except FileNotFoundError:
        logs.error(f'[{file}]文件未找到,请检查路径是否正确')
    except Exception as e:
        logs.error(f'获取【{file}】文件数据时出现未知错误: {str(e)}')


class ReadYamlData:
    """读写接口的YAML格式测试数据"""

    def __init__(self, yaml_file=None):
        if yaml_file is not None:
            self.yaml_file = yaml_file
        else:
            pass
        self.conf = OperationConfig()
        self.yaml_data = None

    @property
    def get_yaml_data(self):
        """
        获取测试用例yaml数据
        :param file: YAML文件
        :return: 返回list
        """
        # Loader=yaml.FullLoader表示加载完整的YAML语言,避免任意代码执行,无此参数控制台报Warning
        try:
            with open(self.yaml_file, 'r', encoding='utf-8') as f:
                self.yaml_data = yaml.safe_load(f)
                return self.yaml_data
        except Exception:
            logs.error(str(traceback.format_exc()))

    def write_yaml_data(self, value):
        """
        写入数据需为dict,allow_unicode=True表示写入中文,sort_keys按顺序写入
        写入YAML文件数据,主要用于接口关联
        :param value: 写入数据,必须用dict
        :return:
        """

        file = None
        file_path = FILE_PATH['EXTRACT']
        if not os.path.exists(file_path):
            os.system(file_path)
        try:
            file = open(file_path, 'a', encoding='utf-8')
            if isinstance(value, dict):
                write_data = yaml.dump(value, allow_unicode=True, sort_keys=False)
                file.write(write_data)
            else:
                logs.info('写入[extract.yaml]的数据必须为dict格式')
        except Exception:
            logs.error(str(traceback.format_exc()))
        finally:
            file.close()

    def clear_yaml_data(self):
        """
        清空extract.yaml文件数据
        :param filename: yaml文件名
        :return:
        """
        with open(FILE_PATH['EXTRACT'], 'w') as f:
            f.truncate()

    def get_extract_yaml(self, node_name, second_node_name=None):
        """
        用于读取接口提取的变量值
        :param node_name:
        :return:
        """
        if os.path.exists(FILE_PATH['EXTRACT']):
            pass
        else:
            logs.error('extract.yaml不存在')
            file = open(FILE_PATH['EXTRACT'], 'w')
            file.close()
            logs.info('extract.yaml创建成功!')
        try:
            with open(FILE_PATH['EXTRACT'], 'r', encoding='utf-8') as rf:
                ext_data = yaml.safe_load(rf)
                if second_node_name is None:
                    return ext_data[node_name]
                else:
                    return ext_data[node_name][second_node_name]
        except Exception as e:
            logs.error(f"【extract.yaml】没有找到:{node_name},--%s" % e)

    def get_testCase_baseInfo(self, case_info):
        """
        获取testcase yaml文件的baseInfo数据
        :param case_info: yaml数据,dict类型
        :return:
        """
        pass

    def get_method(self):
        """
        :param self:
        :return:
        """
        yal_data = self.get_yaml_data()
        metd = yal_data[0].get('method')
        return metd

    def get_request_parame(self):
        """
        获取yaml测试数据中的请求参数
        :return:
        """
        data_list = []
        yaml_data = self.get_yaml_data()
        del yaml_data[0]
        for da in yaml_data:
            data_list.append(da)
        return data_list
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.

 common下的recordlog.py:

import sys

from conf import setting
import logging
import os
import time
from logging.handlers import RotatingFileHandler  # 按文件大小滚动备份
import datetime

log_path = setting.FILE_PATH["LOG"]
if not os.path.exists(log_path): os.mkdir(log_path)
logfile_name = log_path + r"\test.{}.log".format(time.strftime("%Y%m%d"))


class RecordLog:
    """日志模块"""

    def __init__(self):
        self.handle_overdue_log()

    def handle_overdue_log(self):
        """处理过期日志文件"""
        # 获取系统的当前时间
        now_time = datetime.datetime.now()
        # 日期偏移30天,最多保留30的日志文件,超过自动清理
        offset_date = datetime.timedelta(days=-30)
        # 获取前一天时间戳
        before_date = (now_time + offset_date).timestamp()
        # 找到目录下的文件
        files = os.listdir(log_path)
        for file in files:
            if os.path.splitext(file)[1]:
                filepath = log_path + "\\" + file
                file_create_time = os.path.getctime(filepath)  # 获取文件创建时间,返回时间戳
                # dateArray = datetime.datetime.fromtimestamp(file_createtime) #标准时间格式
                # print(dateArray.strftime("%Y--%m--%d %H:%M:%S"))
                if file_create_time < before_date:
                    os.remove(filepath)
                else:
                    continue

    def output_logging(self):
        """获取logger对象"""
        logger = logging.getLogger(__name__)
        # 防止重复打印日志
        if not logger.handlers:
            logger.setLevel(setting.LOG_LEVEL)
            log_format = logging.Formatter(
                '%(levelname)s - %(asctime)s - %(filename)s:%(lineno)d -[%(module)s:%(funcName)s] - %(message)s')
            # 日志输出到指定文件,滚动备份日志
            fh = RotatingFileHandler(filename=logfile_name, mode='a', maxBytes=5242880,
                                     backupCount=7,
                                     encoding='utf-8')  # maxBytes:控制单个日志文件的大小,单位是字节,backupCount:用于控制日志文件的数量

            fh.setLevel(setting.LOG_LEVEL)
            fh.setFormatter(log_format)
            # 将相应的handler添加在logger对象中
            logger.addHandler(fh)

            # 输出到控制台
            sh = logging.StreamHandler()
            sh.setLevel(setting.STREAM_LOG_LEVEL)
            sh.setFormatter(log_format)
            logger.addHandler(sh)
        return logger


apilog = RecordLog()
logs = apilog.output_logging()
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.

common下的semail.py:

import smtplib
from email.mime.text import MIMEText
from conf.operationConfig import OperationConfig
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication  # 附件
from conf import setting
from common.recordlog import logs
import re

conf = OperationConfig()


class SendEmail(object):
    """构建邮件主题、正文、附件"""

    def __init__(
            self,
            host=conf.get_section_for_data('EMAIL', 'host'),
            user=conf.get_section_for_data('EMAIL', 'user'),
            passwd=conf.get_section_for_data('EMAIL', 'passwd')):
        self.__host = host
        self.__user = user
        self.__passwd = passwd

    def build_content(self, subject, email_content, addressee=None, atta_file=None):
        """
        构建邮件格式,邮件正文、附件
        @param subject: 邮件主题
        @param addressee: 收件人,在配置文件中以;分割
        @param email_content: 邮件正文内容
        @return:
        """
        user = 'liaison officer' + '<' + self.__user + '>'
        # 收件人
        if addressee is None:
            addressee = conf.get_section_for_data('EMAIL', 'addressee').split(';')
        else:
            addressee = addressee.split(';')
        message = MIMEMultipart()
        message['Subject'] = subject
        message['From'] = user
        message['To'] = ';'.join([re.search(r'(.*)(@)', emi).group(1) + "<" + emi + ">" for emi in addressee])

        # 邮件正文
        text = MIMEText(email_content, _subtype='plain', _charset='utf-8')
        message.attach(text)

        if atta_file is not None:
            # 附件
            atta = MIMEApplication(open(atta_file, 'rb').read())
            atta['Content-Type'] = 'application/octet-stream'
            atta['Content-Disposition'] = 'attachment; filename="testresult.xls"'
            message.attach(atta)

        try:
            service = smtplib.SMTP_SSL(self.__host)
            service.login(self.__user, self.__passwd)
            service.sendmail(user, addressee, message.as_string())
        except smtplib.SMTPConnectError as e:
            logs.error('邮箱服务器连接失败!', e)
        except smtplib.SMTPAuthenticationError as e:
            logs.error('邮箱服务器认证错误,POP3/SMTP服务未开启,密码应填写授权码!', e)
        except smtplib.SMTPSenderRefused as e:
            logs.error('发件人地址未经验证!', e)
        except smtplib.SMTPDataError as e:
            logs.error('发送的邮件内容包含了未被许可的信息,或被系统识别为垃圾邮件!', e)
        except Exception as e:
            logs.error(e)
        else:
            logs.info('邮件发送成功!')
            service.quit()


class BuildEmail(SendEmail):
    """发送邮件"""

    # def __int__(self, host, user, passwd):
    #     super(BuildEmail, self).__init__(host, user, passwd)

    def main(self, success, failed, error, not_running, atta_file=None, *args):
        """
        :param success: list类型
        :param failed: list类型
        :param error: list类型
        :param not_running: list类型
        :param atta_file: 附件路径
        :param args:
        :return:
        """
        success_num = len(success)
        fail_num = len(failed)
        error_num = len(error)
        notrun_num = len(not_running)
        total = success_num + fail_num + error_num + notrun_num
        execute_case = success_num + fail_num
        pass_result = "%.2f%%" % (success_num / execute_case * 100)
        fail_result = "%.2f%%" % (fail_num / execute_case * 100)
        err_result = "%.2f%%" % (error_num / execute_case * 100)
        # 设置邮件主题、收件人、内容
        subject = conf.get_section_for_data('EMAIL', 'subject')
        addressee = conf.get_section_for_data('EMAIL', 'addressee').split(';')
        content = "     ***项目接口测试,共测试接口%s个,通过%s个,失败%s个,错误%s个,未执行%s个,通过率%s,失败率%s,错误率%s。" \
                  "详细测试结果请参见附件。" % (
                      total, success_num, fail_num, error_num, notrun_num, pass_result, fail_result, err_result)
        self.build_content(addressee, subject, content, atta_file)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.

common下的sendrequest里封装的http请求:

import json
import allure
import pytest
import requests
import urllib3
import time

from conf import setting
from common.recordlog import logs
from requests import utils
from common.readyaml import ReadYamlData
from requests.packages.urllib3.exceptions import InsecureRequestWarning


class SendRequest:
    """发送接口请求,暂时只写了get和post方法的请求"""

    def __init__(self, cookie=None):
        self.cookie = cookie
        self.read = ReadYamlData()

    def get(self, url, data, header):
        """
        :param url: 接口地址
        :param data: 请求参数
        :param header: 请求头
        :return:
        """
        requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        try:
            if data is None:
                response = requests.get(url, headers=header, cookies=self.cookie, verify=False)
            else:
                response = requests.get(url, data, headers=header, cookies=self.cookie, verify=False)
        except requests.RequestException as e:
            logs.error(e)
            return None
        except Exception as e:
            logs.error(e)
            return None
        # 响应时间/毫秒
        res_ms = response.elapsed.microseconds / 1000
        # 响应时间/秒
        res_second = response.elapsed.total_seconds()
        response_dict = dict()

        # 接口响应状态码
        response_dict['code'] = response.status_code
        # 接口响应文本
        response_dict['text'] = response.text
        try:
            response_dict['body'] = response.json().get('body')
        except Exception:
            response_dict['body'] = ''
        response_dict['res_ms'] = res_ms
        response_dict['res_second'] = res_second
        return response_dict

    def post(self, url, data, header):
        """
        :param url:
        :param data: verify=False忽略SSL证书验证
        :param header:
        :return:
        """
        # 控制台输出InsecureRequestWarning错误
        requests.packages.urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        try:
            if data is None:
                response = requests.post(url, header, cookies=self.cookie, verify=False)
            else:
                response = requests.post(url, data, headers=header, cookies=self.cookie, verify=False)
        except requests.RequestException as e:
            logs.error(e)
            return None
        except Exception as e:
            logs.error(e)
            return None
        # 响应时间/毫秒
        res_ms = response.elapsed.microseconds / 1000
        # 响应时间/秒
        res_second = response.elapsed.total_seconds()
        response_dict = dict()
        # 接口响应状态码
        response_dict['code'] = response.status_code
        # 接口响应文本
        response_dict['text'] = response.text
        try:
            response_dict['body'] = response.json().get('body')
        except Exception:
            response_dict['body'] = ''
        response_dict['res_ms'] = res_ms
        response_dict['res_second'] = res_second
        return response_dict

    def send_request(self, **kwargs):

        session = requests.session()
        result = None
        cookie = {}
        try:
            result = session.request(**kwargs)
            set_cookie = requests.utils.dict_from_cookiejar(result.cookies)
            if set_cookie:
                cookie['Cookie'] = set_cookie
                self.read.write_yaml_data(cookie)
                logs.info("cookie:%s" % cookie)
            logs.info("接口返回信息:%s" % result.text if result.text else result)
        except requests.exceptions.ConnectionError:
            logs.error("ConnectionError--连接异常")
            pytest.fail("接口请求异常,可能是request的连接数过多或请求速度过快导致程序报错!")
        except requests.exceptions.HTTPError:
            logs.error("HTTPError--http异常")
        except requests.exceptions.RequestException as e:
            logs.error(e)
            pytest.fail("请求异常,请检查系统或数据是否正常!")
        return result

    def run_main(self, name, url, case_name, header, method, cookies=None, file=None, **kwargs):
        """
        接口请求
        :param name: 接口名
        :param url: 接口地址
        :param case_name: 测试用例
        :param header:请求头
        :param method:请求方法
        :param cookies:默认为空
        :param file: 上传文件接口
        :param kwargs: 请求参数,根据yaml文件的参数类型
        :return:
        """

        try:
            # 收集报告日志
            logs.info('接口名称:%s' % name)
            logs.info('请求地址:%s' % url)
            logs.info('请求方式:%s' % method)
            logs.info('测试用例名称:%s' % case_name)
            logs.info('请求头:%s' % header)
            logs.info('Cookie:%s' % cookies)
            req_params = json.dumps(kwargs, ensure_ascii=False)
            if "data" in kwargs.keys():
                allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                logs.info("请求参数:%s" % kwargs)
            elif "json" in kwargs.keys():
                allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                logs.info("请求参数:%s" % kwargs)
            elif "params" in kwargs.keys():
                allure.attach(req_params, '请求参数', allure.attachment_type.TEXT)
                logs.info("请求参数:%s" % kwargs)
        except Exception as e:
            logs.error(e)
        # time.sleep(0.5)
        requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
        response = self.send_request(method=method,
                                     url=url,
                                     headers=header,
                                     cookies=cookies,
                                     files=file,
                                     timeout=setting.API_TIMEOUT,
                                     verify=False,
                                     **kwargs)
        return response
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  • 96.
  • 97.
  • 98.
  • 99.
  • 100.
  • 101.
  • 102.
  • 103.
  • 104.
  • 105.
  • 106.
  • 107.
  • 108.
  • 109.
  • 110.
  • 111.
  • 112.
  • 113.
  • 114.
  • 115.
  • 116.
  • 117.
  • 118.
  • 119.
  • 120.
  • 121.
  • 122.
  • 123.
  • 124.
  • 125.
  • 126.
  • 127.
  • 128.
  • 129.
  • 130.
  • 131.
  • 132.
  • 133.
  • 134.
  • 135.
  • 136.
  • 137.
  • 138.
  • 139.
  • 140.
  • 141.
  • 142.
  • 143.
  • 144.
  • 145.
  • 146.
  • 147.
  • 148.
  • 149.
  • 150.
  • 151.
  • 152.
  • 153.
  • 154.
  • 155.
  • 156.
  • 157.
  • 158.
  • 159.
  • 160.
  • 161.
  • 162.
  • 163.

conf下的config.ini:

[api_envi]
host = http://127.0.0.1:8787

[MYSQL]
host = *****
port = 3306
username = root
password = 12345
database = zqsjfx

[REDIS]
host = Redis服务器ip
port = 7005
username = ""
password = ""
db = db0

[CLICKHOUSE]
host = *******
port = 8123
username = default
password =
timeout = 10
db = default

[MongoDB]
host = MongoDB库的ip地址
port = 27017
username = admin
password = 123456
database = admin

[EMAIL]
host = smtp.163.com
port = 25
user = 发件人邮箱地址
passwd = *****这里填邮箱的授权码,不是邮箱登录密码*****
addressee = 收件邮箱地址,多个收件人以;隔开,但是这里不用这个邮件发送,使用的是jenkins自带的邮件发送功能
subject = 接口测试

[SSH]
host = ******
port = 22
username = root
password = *******
timeout = 10
command = cat /logs/web_app/zjjtsjzf/zjjtsjzf-api/common-default.log

;默认为allure,则生成allure报告,若=tm,则生成tmreport报告
[REPORT_TYPE]
type = allure
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.

config下的operrationconfig:

import sys
import traceback

# sys.path.insert(0, "..")

import configparser
from conf import setting
from common.recordlog import logs


class OperationConfig:
    """封装读取*.ini配置文件模块"""

    def __init__(self, filepath=None):

        if filepath is None:
            self.__filepath = setting.FILE_PATH['CONFIG']
        else:
            self.__filepath = filepath

        self.conf = configparser.ConfigParser()
        try:
            self.conf.read(self.__filepath, encoding='utf-8')
        except Exception as e:
            exc_type, exc_value, exc_obj = sys.exc_info()
            logs.error(str(traceback.print_exc(exc_obj)))

        self.type = self.get_report_type('type')

    def get_item_value(self, section_name):
        """
        :param section_name: 根据ini文件的头部值获取全部值
        :return:以字典形式返回
        """
        items = self.conf.items(section_name)
        return dict(items)

    def get_section_for_data(self, section, option):
        """
        :param section: ini文件头部值
        :param option:头部值下面的选项
        :return:
        """
        try:
            values = self.conf.get(section, option)
            return values
        except Exception as e:
            logs.error(str(traceback.format_exc()))
            return ''

    def write_config_data(self, section, option_key, option_value):
        """
        写入数据到ini配置文件中
        :param section: 头部值
        :param option_key: 选项值key
        :param option_value: 选项值value
        :return:
        """
        if section not in self.conf.sections():
            # 添加一个section值
            self.conf.add_section(section)
            self.conf.set(section, option_key, option_value)
        else:
            logs.info('"%s"值已存在,写入失败' % section)
        with open(self.__filepath, 'w', encoding='utf-8') as f:
            self.conf.write(f)

    def get_section_mysql(self, option):
        return self.get_section_for_data("MYSQL", option)

    def get_section_redis(self, option):
        return self.get_section_for_data("REDIS", option)

    def get_section_clickhouse(self, option):
        return self.get_section_for_data("CLICKHOUSE", option)

    def get_section_mongodb(self, option):
        return self.get_section_for_data("MongoDB", option)

    def get_report_type(self, option):
        return self.get_section_for_data('REPORT_TYPE', option)

    def get_section_ssh(self, option):
        return self.get_section_for_data("SSH", option)
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.

 config下的setting.py

import logging
import os
import sys

DIR_BASE = os.path.dirname(os.path.dirname(__file__))
sys.path.append(DIR_BASE)

# log日志输出级别
LOG_LEVEL = logging.DEBUG  # 文件
STREAM_LOG_LEVEL = logging.DEBUG  # 控制台

# 接口超时时间,单位/s
API_TIMEOUT = 60

# excel文件的sheet页,默认读取第一个sheet页的数据,int类型,第一个sheet为0,以此类推0.....9
SHEET_ID = 0

# 生成的测试报告类型,可以生成两个风格的报告,allure或tm
REPORT_TYPE = 'allure'

# 是否发送钉钉消息
dd_msg = False

# 文件路径
FILE_PATH = {
    'CONFIG': os.path.join(DIR_BASE, 'conf/config.ini'),
    'LOG': os.path.join(DIR_BASE, 'logs'),
    'YAML': os.path.join(DIR_BASE),
    'TEMP': os.path.join(DIR_BASE, 'report/temp'),
    'TMR': os.path.join(DIR_BASE, 'report/tmreport'),
    'EXTRACT': os.path.join(DIR_BASE, 'extract.yaml'),
    'XML': os.path.join(DIR_BASE, 'data/sql'),
    'RESULTXML': os.path.join(DIR_BASE, 'report'),
    'EXCEL': os.path.join(DIR_BASE, 'data', '测试数据.xls')
}

# 默认请求头信息
LOGIN_HEADER = {
    'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
    'Accept': 'application/json, text/plain, */*',
    'Accept-Language': 'zh-CN,zh;q=0.9',
    'Connection': 'keep-alive'
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.

data下的sql里面的login_data.csv:

admin,c82b67810509fe2ac39d92e140d58d25d6b14776
admin123,c82b67810509fe2ac39d92e140d58d25d6b14776
admin,admin123456
  • 1.
  • 2.
  • 3.

data下的sql里面的login_name.yaml:

- baseInfo:
    api_name: 用户登录
    url: /dar/user/login
    method: post
    header:
      Content-Type: application/x-www-form-urlencoded;charset=UTF-8
  testCase:
    - case_name: 用户名和密码正确登录验证
      data:
        user_name: test01
        passwd: admin123
      validation:
        - contains: { 'error_code': none }
        - eq: { 'msg': '登录成功' }
      extract:
        token: $.token
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.