• 接口自动化框架开发过程记录帖(更新完结)


    背景介绍

    趁着休息的时间,把以前的接口自动化框架再优化一下,等全部功能模块改完后再把东西放到github上,现在开个记录帖。

    正文

    有时候也会问自己为什么要重复造轮子,开源框架一搜一堆。后来想想,可能我在乎的不是目的地,而是沿途的风景。

    【流程图】

    总体的框架流程图如下所示:

    【Common】

    常见的接口都是走http协议,对requests库进行post/get请求方法的封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:Request.py
    @E-mail:364942727@qq.com
    @Time:2020/9/5 8:29 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:Request请求封装模块
    """
    
    import requests
    from Common.Log import logger
    
    
    class RunMain():
        def __init__(self):
            self.logger = logger
    
        def send_post(self, url, headers, data):  # 定义一个方法,传入需要的参数url、headers和data
            # 参数必须按照url、headers、data顺序传入
            headers = headers
            result_data = requests.post(url=url, headers=headers, data=data).json()  # 因为这里要封装post方法,所以这里的url和data值不能写死
            result_json = requests.post(url=url, headers=headers, json=data).json()  # 接口需要json参数提交数据,用这种请求方法
            # res = json.dumps(Log, ensure_ascii=False, sort_keys=True, indent=2)  # 格式化输出
            res = result_data
            return res
    
        def send_get(self, url, headers, data):
            headers = headers
            result_data = requests.get(url=url, headers=headers, data=data).json()
            result_json = requests.post(url=url, headers=headers, json=data).json()  # 接口需要json参数提交数据,用这种请求方法
            # res = json.dumps(Log, ensure_ascii=False, sort_keys=True, indent=2)  # 格式化输出
            res = result_data
            return res
    
        def run_main(self, method, url=None, headers=None, data=None):  # 定义一个run_main函数,通过传过来的method来进行不同的get或post请求
            result = None
            if method == 'post':
                result = self.send_post(url, headers, data)
                self.logger.info(str(result))
            elif method == 'get':
                result = self.send_get(url, headers, data)
                self.logger.info(str(result))
            else:
                print("method值错误!!!")
                self.logger.info("method值错误!!!")
            return result
    
    
    if __name__ == '__main__':  # 通过写死参数,来验证我们写的请求是否正确
        pass
        # method_post = 'post'
        # url_post = 'http://127.0.0.1:5000/login'
        # data_post = {
        #     "username": "admin",
        #     "password": "a123456"
        # }
        # result_post = RunMain().run_main(method=method_post, url=url_post, data=data_post)
        # print(result_post)

    对发送邮件的SMTP模块进行封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:SendEmail.py
    @E-mail:364942727@qq.com
    @Time:2020/9/5 7:58 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:封装SMTP邮件功能模块
    """
    
    import os
    from Config import readConfig
    import getpathInfo
    import smtplib
    from email.mime.multipart import MIMEMultipart
    from email.mime.text import MIMEText
    from email.header import Header
    from Common.Log import logger
    
    
    class SendEmail(object):
        def __init__(self):
            # 读取邮件配置信息,初始化参数
            read_conf = readConfig.ReadConfig()
            self.email_service = read_conf.get_email('EMAIL_SERVICE')  # 从配置文件中读取,邮件服务器类型
            self.email_port = read_conf.get_email('EMAIL_PORT')  # 从配置文件中读取,邮件服务器端口
            self.sender_address = read_conf.get_email('SENDER_ADDRESS')  # 从配置文件中读取,发件人邮箱地址
            self.sender_password = read_conf.get_email('SENDER_PASSWORD')  # 从配置文件中读取,发件人邮箱授权码
            self.receiver_address = read_conf.get_email('RECEIVER_ADDRESS')  # 从配置文件中读取,收件人邮箱地址
            self.file_path = os.path.join(getpathInfo.get_Path(), 'Report', 'report.html')  # 获取测试报告路径
            # 日志输出
            self.logger = logger
    
        def send_email(self):
            # 第三方 SMTP 服务
            message = MIMEMultipart()
            # 创建附件的实例
            message['From'] = Header("测试组", 'utf-8')
            message['To'] = Header(''.join(self.receiver_address), 'utf-8')
            subject = '接口测试邮件'
            message['Subject'] = Header(subject, 'utf-8')
            # 邮件正文内容
            part = MIMEText('Dear all:
           附件为接口自动化测试报告,此为自动发送邮件,请勿回复,谢谢!', 'plain', 'utf-8')
            message.attach(part)
            # 发送附件
            att1 = MIMEText(open(file=self.file_path, mode='r').read(), 'base64', 'utf-8')
            att1["Content-Type"] = 'application/octet-stream'
            att1.add_header('Content-Disposition', 'attachment', filename=('utf-8', '', '接口测试报告.html'))
            message.attach(att1)
    
            try:
    
                service = smtplib.SMTP_SSL(self.email_service)
                # service.set_debuglevel(True)  # debug开启或关闭
                service.connect(self.email_service, self.email_port)
                service.login(self.sender_address, self.sender_password)
                service.sendmail(self.sender_address, self.receiver_address, message.as_string())
                print('邮件发送成功')
                service.close()
                self.logger.info("{'邮件发送成功'}")
    
            except smtplib.SMTPException:
                print("报错,邮件发送失败")
                self.logger.info("{'报错,邮件发送失败'}")
    
    
    if __name__ == '__main__':
        # SendEmail().send_email()  # 测试邮件功能模块
        pass

    常见assert断言模块的封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:Assert.py
    @E-mail:364942727@qq.com
    @Time:2020/9/5 23:03 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:Assert断言封装模块
    """
    
    from Common.Log import logger
    import json
    
    
    class Assertions:
        def __init__(self):
            self.log = logger
    
        def assert_code(self, code, expected_code):
            """
            验证response状态码
            :param code:
            :param expected_code:
            :return:
            """
            try:
                assert code == expected_code
                return True
            except:
                self.log.info("statusCode error, expected_code is %s, statusCode is %s " % (expected_code, code))
    
                raise
    
        def assert_body(self, body, body_msg, expected_msg):
            """
            验证response body中任意属性的值
            :param body:
            :param body_msg:
            :param expected_msg:
            :return:
            """
            try:
                msg = body[body_msg]
                assert msg == expected_msg
                return True
    
            except:
                self.log.info(
                    "Response body msg != expected_msg, expected_msg is %s, body_msg is %s" % (expected_msg, body_msg))
    
                raise
    
        def assert_in_text(self, body, expected_msg):
            """
            验证response body中是否包含预期字符串
            :param body:
            :param expected_msg:
            :return:
            """
            try:
                text = json.dumps(body, ensure_ascii=False)
                # print(text)
                assert expected_msg in text
                return True
    
            except:
                self.log.info("Response body Does not contain expected_msg, expected_msg is %s" % expected_msg)
    
                raise
    
        def assert_text(self, body, expected_msg):
            """
            验证response body中是否等于预期字符串
            :param body:
            :param expected_msg:
            :return:
            """
            try:
                assert body == expected_msg
                return True
    
            except:
                self.log.info("Response body != expected_msg, expected_msg is %s, body is %s" % (expected_msg, body))
    
                raise
    
        def assert_time(self, time, expected_time):
            """
            验证response body响应时间小于预期最大响应时间,单位:毫秒
            :param body:
            :param expected_time:
            :return:
            """
            try:
                assert time < expected_time
                return True
    
            except:
                self.log.info("Response time > expected_time, expected_time is %s, time is %s" % (expected_time, time))
    
                raise
    
    
    if __name__ == '__main__':
        # info_body = {'code': 102001, 'message': 'login success'}
        # Assert = Assertions()
        # expect_code = 10200
        # Assert.assert_code(info_body['code'], expect_code)
        pass

    对Log日志模块的封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:Log.py
    @E-mail:364942727@qq.com
    @Time:2020/9/4 8:58 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:Log日志模块
    """
    
    import os
    import logging
    from logging.handlers import TimedRotatingFileHandler
    import getpathInfo
    
    
    class Logger(object):
        def __init__(self, logger_name='logs…'):
            global log_path
            path = getpathInfo.get_Path()
            log_path = os.path.join(path, 'Log')  # 存放log文件的路径
            self.logger = logging.getLogger(logger_name)
            logging.root.setLevel(logging.NOTSET)
            self.log_file_name = 'logs'  # 日志文件的名称
            self.backup_count = 5  # 最多存放日志的数量
            # 日志输出级别
            self.console_output_level = 'WARNING'
            self.file_output_level = 'DEBUG'
            # 日志输出格式
            self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    
        def get_logger(self):
            """在logger中添加日志句柄并返回,如果logger已有句柄,则直接返回"""
            if not self.logger.handlers:  # 避免重复日志
                console_handler = logging.StreamHandler()
                console_handler.setFormatter(self.formatter)
                console_handler.setLevel(self.console_output_level)
                self.logger.addHandler(console_handler)
    
                # 每天重新创建一个日志文件,最多保留backup_count份
                file_handler = TimedRotatingFileHandler(filename=os.path.join(log_path, self.log_file_name), when='D',
                                                        interval=1, backupCount=self.backup_count, delay=True,
                                                        encoding='utf-8')
                file_handler.setFormatter(self.formatter)
                file_handler.setLevel(self.file_output_level)
                self.logger.addHandler(file_handler)
            return self.logger
    
    
    logger = Logger().get_logger()
    
    if __name__ == "__main__":
        pass

    对各种常见加密方法的封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:Hash.py
    @E-mail:364942727@qq.com
    @Time:2020/9/6 15:55 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:封装各种常用的加密方法
    """
    
    from hashlib import sha1
    from hashlib import md5
    from Crypto.Hash import SHA256
    from Crypto.Cipher import AES
    from Crypto.Cipher import DES
    import binascii
    
    
    class MyHash(object):
    
        def my_md5(self, msg):
            """
            md5 算法加密
            :param msg: 需加密的字符串
            :return: 加密后的字符
            """
            hl = md5()
            hl.update(msg.encode('utf-8'))
            return hl.hexdigest()
    
        def my_sha1(self, msg):
            """
            sha1 算法加密
            :param msg: 需加密的字符串
            :return: 加密后的字符
            """
            sh = sha1()
            sh.update(msg.encode('utf-8'))
            return sh.hexdigest()
    
        def my_sha256(self, msg):
            """
            sha256 算法加密
            :param msg: 需加密的字符串
            :return: 加密后的字符
            """
            sh = SHA256.new()
            sh.update(msg.encode('utf-8'))
            return sh.hexdigest()
    
        def my_des(self, msg, key):
            """
            DES 算法加密
            :param msg: 需加密的字符串,长度必须为8的倍数,不足添加'='
            :param key: 8个字符
            :return: 加密后的字符
            """
            de = DES.new(key, DES.MODE_ECB)
            mss = msg + (8 - (len(msg) % 8)) * '='
            text = de.encrypt(mss.encode())
            return binascii.b2a_hex(text).decode()
    
        def my_aes_encrypt(self, msg, key, vi):
            """
            AES 算法的加密
            :param msg: 需加密的字符串
            :param key: 必须为16,24,32位
            :param vi: 必须为16位
            :return: 加密后的字符
            """
            obj = AES.new(key, AES.MODE_CBC, vi)
            txt = obj.encrypt(msg.encode())
            return binascii.b2a_hex(txt).decode()
    
        def my_aes_decrypt(self, msg, key, vi):
            """
            AES 算法的解密
            :param msg: 需解密的字符串
            :param key: 必须为16,24,32位
            :param vi: 必须为16位
            :return: 加密后的字符
            """
            msg = binascii.a2b_hex(msg)
            obj = AES.new(key, AES.MODE_CBC, vi)
            return obj.decrypt(msg).decode()
    
    
    if __name__ == "__main__":
        res = MyHash().my_md5('hello world')
        print(res)

    获取配置文件中拼接后的base_url

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:geturlParams.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 9:28 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:获取配置文件中拼接后的URL
    """
    
    from Config import readConfig as readConfig
    
    
    class geturlParams():  # 定义一个方法,将从配置文件中读取的进行拼接
        def __init__(self):
            self.readconfig = readConfig.ReadConfig()
    
        def get_Url(self):
            new_url = self.readconfig.get_http('scheme') + '://' + self.readconfig.get_http(
                'baseurl') + ':' + self.readconfig.get_http(
                'port')
            # logger.info('new_url'+new_url)
            return new_url
    
    
    if __name__ == '__main__':  # 验证拼接后的正确性
        print(geturlParams().get_Url())
        # pass

    对读取Excel文件方法的封装。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:readExcel.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 16:58 上午
    @Author:Nobita
    @Version:1.0
    @Desciption:
    """
    
    import os
    import getpathInfo
    from xlrd import open_workbook  # 调用读Excel的第三方库xlrd
    
    
    class readExcel():
        def __init__(self):
            self.path = getpathInfo.get_Path()  # 拿到该项目所在的绝对路径
    
        def get_xls(self, xls_name, sheet_name):  # xls_name填写用例的Excel名称 sheet_name该Excel的sheet名称
            cls = []
            # 获取用例文件路径
            xlsPath = os.path.join(self.path, "TestFile", 'case', xls_name)
            file = open_workbook(xlsPath)  # 打开用例Excel
            sheet = file.sheet_by_name(sheet_name)  # 获得打开Excel的sheet
            # 获取这个sheet内容行数
            nrows = sheet.nrows
            for i in range(nrows):  # 根据行数做循环
                if sheet.row_values(i)[0] != u'case_name':  # 如果这个Excel的这个sheet的第i行的第一列不等于case_name那么我们把这行的数据添加到cls[]
                    cls.append(sheet.row_values(i))
            return cls
    
    
    if __name__ == '__main__':  # 我们执行该文件测试一下是否可以正确获取Excel中的值
        print(readExcel().get_xls('learning-API-test_Case.xlsx', 'login'))  # 遍历每一行数据
        print(readExcel().get_xls('learning-API-test_Case.xlsx', 'login')[0][1])  # 登录接口url
        print(readExcel().get_xls('learning-API-test_Case.xlsx', 'login')[1][4])  # 请求method
        # pass

    对生成html接口自动化报告方法的封装。

    #coding=utf-8
    """
    A TestRunner for use with the Python unit testing framework. It
    generates a HTML report to show the Log at a glance.
    
    The simplest way to use this is to invoke its main method. E.g.
    
        import unittest
        import HTMLTestReportCN
    
        ... define your tests ...
    
        if __name__ == '__main__':
            HTMLTestReportCN.main()
    
    
    For more customization options, instantiates a HTMLTestReportCN object.
    HTMLTestReportCN is a counterpart to unittest's TextTestRunner. E.g.
    
        # output to a file
        fp = file('my_report.html', 'wb')
        runner = HTMLTestReportCN.HTMLTestReportCN(
                    stream=fp,
                    title='My unit test',
                    description='This demonstrates the report output by HTMLTestReportCN.'
                    )
    
        # Use an external stylesheet.
        # See the Template_mixin class for more customizable options
        runner.STYLESHEET_TMPL = '<link rel="stylesheet" href="my_stylesheet.css" type="text/css">'
    
        # run the test
        runner.run(my_test_suite)
    
    
    ------------------------------------------------------------------------
    Copyright (c) 2004-2007, Wai Yip Tung
    Copyright (c) 2017, Findyou
    All rights reserved.
    
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:
    
    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name Wai Yip Tung nor the names of its contributors may be
      used to endorse or promote products derived from this software without
      specific prior written permission.
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
    OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    """
    
    # URL: http://tungwaiyip.info/software/HTMLTestRunner.html
    
    __author__ = "Wai Yip Tung,  Findyou"
    __version__ = "0.8.3"
    
    
    """
    Change History
    Version 0.8.3 -Findyou 20171206
    * BUG fixed :错误的测试用例没有统计与显示
    * BUG fixed :当PASS的测试用例有print内容时,通过按钮显示为红色
    * 表格背景颜色根据用例结果显示颜色,优先级: 错误(黄色)>失败(红色)>通过(绿色)
    * 合并文为HTMLTestRunner*N.py 同时支持python2,python3
    
    Version 0.8.2.2 -Findyou
    * HTMLTestRunnerEN.py 支持 python3.x
    * HTMLTestRunnerEN.py 支持 python2.x
    
    Version 0.8.2.1 -Findyou
    * 支持中文,汉化
    * 调整样式,美化(需要连入网络,使用的百度的Bootstrap.js)
    * 增加 通过分类显示、测试人员、通过率的展示
    * 优化“详细”与“收起”状态的变换
    * 增加返回顶部的锚点
    
    Version 0.8.2
    * Show output inline instead of popup window (Viorel Lupu).
    
    Version in 0.8.1
    * Validated XHTML (Wolfgang Borgert).
    * Added description of test classes and test cases.
    
    Version in 0.8.0
    * Define Template_mixin class for customization.
    * Workaround a IE 6 bug that it does not treat <script> block as CDATA.
    
    Version in 0.7.1
    * Back port to Python 2.3 (Frank Horowitz).
    * Fix missing scroll bars in detail log (Podi).
    """
    
    # TODO: color stderr
    # TODO: simplify javascript using ,ore than 1 class in the class attribute?
    
    import datetime
    try:
        from StringIO import StringIO
    except ImportError:
        from io import StringIO
    import sys
    import time
    import unittest
    from xml.sax import saxutils
    
    try:
        reload(sys)
        sys.setdefaultencoding('utf-8')
    except NameError:
        pass
    
    # ------------------------------------------------------------------------
    # The redirectors below are used to capture output during testing. Output
    # sent to sys.stdout and sys.stderr are automatically captured. However
    # in some cases sys.stdout is already cached before HTMLTestRunner is
    # invoked (e.g. calling logging.basicConfig). In order to capture those
    # output, use the redirectors for the cached stream.
    #
    # e.g.
    #   >>> logging.basicConfig(stream=HTMLTestRunner.stdout_redirector)
    #   >>>
    
    class OutputRedirector(object):
        """ Wrapper to redirect stdout or stderr """
        def __init__(self, fp):
            self.fp = fp
    
        def write(self, s):
            self.fp.write(s)
    
        def writelines(self, lines):
            self.fp.writelines(lines)
    
        def flush(self):
            self.fp.flush()
    
    stdout_redirector = OutputRedirector(sys.stdout)
    stderr_redirector = OutputRedirector(sys.stderr)
    
    # ----------------------------------------------------------------------
    # Template
    
    class Template_mixin(object):
        """
        Define a HTML template for report customerization and generation.
    
        Overall structure of an HTML report
    
        HTML
        +------------------------+
        |<html>                  |
        |  <head>                |
        |                        |
        |   STYLESHEET           |
        |   +----------------+   |
        |   |                |   |
        |   +----------------+   |
        |                        |
        |  </head>               |
        |                        |
        |  <body>                |
        |                        |
        |   HEADING              |
        |   +----------------+   |
        |   |                |   |
        |   +----------------+   |
        |                        |
        |   REPORT               |
        |   +----------------+   |
        |   |                |   |
        |   +----------------+   |
        |                        |
        |   ENDING               |
        |   +----------------+   |
        |   |                |   |
        |   +----------------+   |
        |                        |
        |  </body>               |
        |</html>                 |
        +------------------------+
        """
    
        STATUS = {
        0: '通过',
        1: '失败',
        2: '错误',
        }
    
        DEFAULT_TITLE = '测试报告'
        DEFAULT_DESCRIPTION = ''
        DEFAULT_TESTER='QA'
    
        # ------------------------------------------------------------------------
        # HTML Template
    
        HTML_TMPL = r"""<?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>%(title)s</title>
        <meta name="generator" content="%(generator)s"/>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
        <link href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" rel="stylesheet">
        <script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js"></script>
        <script src="http://libs.baidu.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
        %(stylesheet)s
    </head>
    <body >
    %(heading)s
    %(report)s
    %(ending)s
    <script language="javascript" type="text/javascript">
    output_list = Array();
    // 修改按钮颜色显示错误问题 --Findyou v0.8.2.3
    
    $("button[id^='btn_pt']").addClass("btn btn-success");
    $("button[id^='btn_ft']").addClass("btn btn-danger");
    $("button[id^='btn_et']").addClass("btn btn-warning");
    
    /*level
    增加分类并调整,增加error按钮事件 --Findyou v0.8.2.3
    0:Pass    //pt none, ft hiddenRow, et hiddenRow
    1:Failed  //pt hiddenRow, ft none, et hiddenRow
    2:Error    //pt hiddenRow, ft hiddenRow, et none
    3:All     //pt none, ft none, et none
    4:Summary //all hiddenRow
    */
    
    //add Error button event --Findyou v0.8.2.3
    function showCase(level) {
        trs = document.getElementsByTagName("tr");
        for (var i = 0; i < trs.length; i++) {
            tr = trs[i];
            id = tr.id;
            if (id.substr(0,2) == 'ft') {
                if (level == 0 || level == 2 || level == 4 ) {
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
            if (id.substr(0,2) == 'pt') {
                if (level == 1 || level == 2 || level == 4) {
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
            if (id.substr(0,2) == 'et') {
                if (level == 0 || level == 1 || level == 4) {
                    tr.className = 'hiddenRow';
                }
                else {
                    tr.className = '';
                }
            }
        }
    
        //加入【详细】切换文字变化 --Findyou
        detail_class=document.getElementsByClassName('detail');
        //console.log(detail_class.length)
        if (level == 3) {
            for (var i = 0; i < detail_class.length; i++){
                detail_class[i].innerHTML="收起"
            }
        }
        else{
                for (var i = 0; i < detail_class.length; i++){
                detail_class[i].innerHTML="详细"
            }
        }
    }
    
    //add Error button event --Findyou v0.8.2.3
    function showClassDetail(cid, count) {
        var id_list = Array(count);
        var toHide = 1;
        for (var i = 0; i < count; i++) {
            tid0 = 't' + cid.substr(1) + '_' + (i+1);
            tid = 'f' + tid0;
            tr = document.getElementById(tid);
            if (!tr) {
                tid = 'p' + tid0;
                tr = document.getElementById(tid);
            }
            if (!tr) {
                tid = 'e' + tid0;
                tr = document.getElementById(tid);
            }
            id_list[i] = tid;
            if (tr.className) {
                toHide = 0;
            }
        }
        for (var i = 0; i < count; i++) {
            tid = id_list[i];
            //修改点击无法收起的BUG,加入【详细】切换文字变化 --Findyou
            if (toHide) {
                document.getElementById(tid).className = 'hiddenRow';
                document.getElementById(cid).innerText = "详细"
            }
            else {
                document.getElementById(tid).className = '';
                document.getElementById(cid).innerText = "收起"
            }
        }
    }
    
    function html_escape(s) {
        s = s.replace(/&/g,'&amp;');
        s = s.replace(/</g,'&lt;');
        s = s.replace(/>/g,'&gt;');
        return s;
    }
    </script>
    </body>
    </html>
    """
        # variables: (title, generator, stylesheet, heading, report, ending)
    
    
        # ------------------------------------------------------------------------
        # Stylesheet
        #
        # alternatively use a <link> for external style sheet, e.g.
        #   <link rel="stylesheet" href="$url" type="text/css">
    
        STYLESHEET_TMPL = """
    <style type="text/css" media="screen">
    body        { font-family: Microsoft YaHei,Tahoma,arial,helvetica,sans-serif;padding: 20px; font-size: 100%; }
    table       { font-size: 100%; }
    
    /* -- heading ---------------------------------------------------------------------- */
    .heading {
        margin-top: 0ex;
        margin-bottom: 1ex;
    }
    
    .heading .description {
        margin-top: 4ex;
        margin-bottom: 6ex;
    }
    
    /* -- report ------------------------------------------------------------------------ */
    #total_row  { font-weight: bold; }
    .passCase   { color: #5cb85c; }
    .failCase   { color: #d9534f; font-weight: bold; }
    .errorCase  { color: #f0ad4e; font-weight: bold; }
    .hiddenRow  { display: none; }
    .testcase   { margin-left: 2em; }
    </style>
    """
    
        # ------------------------------------------------------------------------
        # Heading
        #
    
        HEADING_TMPL = """<div class='heading'>
    <h1 style="font-family: Microsoft YaHei">%(title)s</h1>
    %(parameters)s
    <p class='description'>%(description)s</p>
    </div>
    
    """ # variables: (title, parameters, description)
    
        HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s : </strong> %(value)s</p>
    """ # variables: (name, value)
    
    
    
        # ------------------------------------------------------------------------
        # Report
        #
        # 汉化,加美化效果 --Findyou
        REPORT_TMPL = """
    <p id='show_detail_line'>
    <a class="btn btn-primary" href='javascript:showCase(4)'>概要{ %(passrate)s }</a>
    <a class="btn btn-success" href='javascript:showCase(0)'>通过{ %(Pass)s }</a>
    <a class="btn btn-danger" href='javascript:showCase(1)'>失败{ %(fail)s }</a>
    <a class="btn btn-warning" href='javascript:showCase(2)'>错误{ %(error)s }</a>
    <a class="btn btn-info" href='javascript:showCase(3)'>所有{ %(count)s }</a>
    </p>
    <table id='result_table' class="table table-condensed table-bordered table-hover">
    <colgroup>
    <col align='left' />
    <col align='right' />
    <col align='right' />
    <col align='right' />
    <col align='right' />
    <col align='right' />
    </colgroup>
    <tr id='header_row' class="text-center active" style="font-weight: bold;font-size: 14px;">
        <td>用例集/测试用例</td>
        <td>总计</td>
        <td>通过</td>
        <td>失败</td>
        <td>错误</td>
        <td>详细</td>
    </tr>
    %(test_list)s
    <tr id='total_row' class="text-center info">
        <td>总计</td>
        <td>%(count)s</td>
        <td>%(Pass)s</td>
        <td>%(fail)s</td>
        <td>%(error)s</td>
        <td>通过率:%(passrate)s</td>
    </tr>
    </table>
    """ # variables: (test_list, count, Pass, fail, error ,passrate)
    
        REPORT_CLASS_TMPL = r"""
    <tr class='%(style)s'>
        <td>%(desc)s</td>
        <td class="text-center">%(count)s</td>
        <td class="text-center">%(Pass)s</td>
        <td class="text-center">%(fail)s</td>
        <td class="text-center">%(error)s</td>
        <td class="text-center"><a href="javascript:showClassDetail('%(cid)s',%(count)s)" class="detail" id='%(cid)s'>详细</a></td>
    </tr>
    """ # variables: (style, desc, count, Pass, fail, error, cid)
    
        #有output内容的样式,去掉原来JS效果,美化展示效果  -Findyou v0.8.2.3
        REPORT_TEST_WITH_OUTPUT_TMPL = r"""
    <tr id='%(tid)s' class='%(Class)s'>
        <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
        <td colspan='5' align='center'>
        <!--默认收起output信息 -Findyou
        <button id='btn_%(tid)s' type="button"  class="btn-xs collapsed" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
        <div id='div_%(tid)s' class="collapse">  -->
    
        <!-- 默认展开output信息 -Findyou -->
        <button id='btn_%(tid)s' type="button"  class="btn-xs" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button>
        <div id='div_%(tid)s' class="collapse in">
        <pre>
        %(script)s
        </pre>
        </div>
        </td>
    </tr>
    """ # variables: (tid, Class, style, desc, status)
    
        # 无output内容样式改为button,按钮效果为不可点击  -Findyou v0.8.2.3
        REPORT_TEST_NO_OUTPUT_TMPL = r"""
    <tr id='%(tid)s' class='%(Class)s'>
        <td class='%(style)s'><div class='testcase'>%(desc)s</div></td>
        <td colspan='5' align='center'><button id='btn_%(tid)s' type="button"  class="btn-xs" disabled="disabled" data-toggle="collapse" data-target='#div_%(tid)s'>%(status)s</button></td>
    </tr>
    """ # variables: (tid, Class, style, desc, status)
    
        REPORT_TEST_OUTPUT_TMPL = r"""
    %(id)s: %(output)s
    """ # variables: (id, output)
    
        # ------------------------------------------------------------------------
        # ENDING
        #
        # 增加返回顶部按钮  --Findyou
        ENDING_TMPL = """<div id='ending'>&nbsp;</div>
        <div style=" position:fixed;right:50px; bottom:30px; 20px; height:20px;cursor:pointer">
        <a href="#"><span class="glyphicon glyphicon-eject" style = "font-size:30px;" aria-hidden="true">
        </span></a></div>
        """
    
    # -------------------- The end of the Template class -------------------
    
    
    TestResult = unittest.TestResult
    
    class _TestResult(TestResult):
        # note: _TestResult is a pure representation of results.
        # It lacks the output and reporting ability compares to unittest._TextTestResult.
    
        def __init__(self, verbosity=1):
            TestResult.__init__(self)
            self.stdout0 = None
            self.stderr0 = None
            self.success_count = 0
            self.failure_count = 0
            self.error_count = 0
            self.verbosity = verbosity
    
            # Log is a list of Log in 4 tuple
            # (
            #   Log code (0: success; 1: fail; 2: error),
            #   TestCase object,
            #   Test output (byte string),
            #   stack trace,
            # )
            self.result = []
            #增加一个测试通过率 --Findyou
            self.passrate=float(0)
    
    
        def startTest(self, test):
            TestResult.startTest(self, test)
            # just one buffer for both stdout and stderr
            self.outputBuffer = StringIO()
            stdout_redirector.fp = self.outputBuffer
            stderr_redirector.fp = self.outputBuffer
            self.stdout0 = sys.stdout
            self.stderr0 = sys.stderr
            sys.stdout = stdout_redirector
            sys.stderr = stderr_redirector
    
    
        def complete_output(self):
            """
            Disconnect output redirection and return buffer.
            Safe to call multiple times.
            """
            if self.stdout0:
                sys.stdout = self.stdout0
                sys.stderr = self.stderr0
                self.stdout0 = None
                self.stderr0 = None
            return self.outputBuffer.getvalue()
    
    
        def stopTest(self, test):
            # Usually one of addSuccess, addError or addFailure would have been called.
            # But there are some path in unittest that would bypass this.
            # We must disconnect stdout in stopTest(), which is guaranteed to be called.
            self.complete_output()
    
    
        def addSuccess(self, test):
            self.success_count += 1
            TestResult.addSuccess(self, test)
            output = self.complete_output()
            self.result.append((0, test, output, ''))
            if self.verbosity > 1:
                sys.stderr.write('ok ')
                sys.stderr.write(str(test))
                sys.stderr.write('
    ')
            else:
                sys.stderr.write('.')
    
        def addError(self, test, err):
            self.error_count += 1
            TestResult.addError(self, test, err)
            _, _exc_str = self.errors[-1]
            output = self.complete_output()
            self.result.append((2, test, output, _exc_str))
            if self.verbosity > 1:
                sys.stderr.write('E  ')
                sys.stderr.write(str(test))
                sys.stderr.write('
    ')
            else:
                sys.stderr.write('E')
    
        def addFailure(self, test, err):
            self.failure_count += 1
            TestResult.addFailure(self, test, err)
            _, _exc_str = self.failures[-1]
            output = self.complete_output()
            self.result.append((1, test, output, _exc_str))
            if self.verbosity > 1:
                sys.stderr.write('F  ')
                sys.stderr.write(str(test))
                sys.stderr.write('
    ')
            else:
                sys.stderr.write('F')
    
    
    class HTMLTestReportCN(Template_mixin):
        """
        """
        def __init__(self, stream=sys.stdout, verbosity=1,title=None,description=None,tester=None):
            self.stream = stream
            self.verbosity = verbosity
            if title is None:
                self.title = self.DEFAULT_TITLE
            else:
                self.title = title
            if description is None:
                self.description = self.DEFAULT_DESCRIPTION
            else:
                self.description = description
            if tester is None:
                self.tester = self.DEFAULT_TESTER
            else:
                self.tester = tester
    
            self.startTime = datetime.datetime.now()
    
    
        def run(self, test):
            "Run the given test case or test suite."
            result = _TestResult(self.verbosity)
            test(result)
            self.stopTime = datetime.datetime.now()
            self.generateReport(test, result)
            # print >>sys.stderr, '
    Time Elapsed: %s' % (self.stopTime-self.startTime)
            sys.stderr.write('
    Time Elapsed: %s' % (self.stopTime-self.startTime))
            return result
    
    
        def sortResult(self, result_list):
            # unittest does not seems to run in any particular order.
            # Here at least we want to group them together by class.
            rmap = {}
            classes = []
            for n,t,o,e in result_list:
                cls = t.__class__
                # if not rmap.has_key(cls):
                if cls not in rmap:
                    rmap[cls] = []
                    classes.append(cls)
                rmap[cls].append((n,t,o,e))
            r = [(cls, rmap[cls]) for cls in classes]
            return r
    
        #替换测试结果status为通过率 --Findyou
        def getReportAttributes(self, result):
            """
            Return report attributes as a list of (name, value).
            Override this to add custom attributes.
            """
            startTime = str(self.startTime)[:19]
            duration = str(self.stopTime - self.startTime)
            status = []
            status.append('共 %s' % (result.success_count + result.failure_count + result.error_count))
            if result.success_count: status.append('通过 %s'    % result.success_count)
            if result.failure_count: status.append('失败 %s' % result.failure_count)
            if result.error_count:   status.append('错误 %s'   % result.error_count  )
            if status:
                status = ''.join(status)
            # 合入Github:boafantasy代码
                if (result.success_count + result.failure_count + result.error_count) > 0:
                    self.passrate = str("%.2f%%" % (float(result.success_count) / float(result.success_count + result.failure_count + result.error_count) * 100))
                else:
                    self.passrate = "0.00 %"
            else:
                status = 'none'
            return [
                (u'测试人员', self.tester),
                (u'开始时间',startTime),
                (u'合计耗时',duration),
                (u'测试结果',status + ",通过率= "+self.passrate),
            ]
    
    
        def generateReport(self, test, result):
            report_attrs = self.getReportAttributes(result)
            generator = 'HTMLTestReportCN %s' % __version__
            stylesheet = self._generate_stylesheet()
            heading = self._generate_heading(report_attrs)
            report = self._generate_report(result)
            ending = self._generate_ending()
            output = self.HTML_TMPL % dict(
                title = saxutils.escape(self.title),
                generator = generator,
                stylesheet = stylesheet,
                heading = heading,
                report = report,
                ending = ending,
            )
            self.stream.write(output.encode('utf8'))
    
    
        def _generate_stylesheet(self):
            return self.STYLESHEET_TMPL
    
        #增加Tester显示 -Findyou
        def _generate_heading(self, report_attrs):
            a_lines = []
            for name, value in report_attrs:
                line = self.HEADING_ATTRIBUTE_TMPL % dict(
                        name = saxutils.escape(name),
                        value = saxutils.escape(value),
                    )
                a_lines.append(line)
            heading = self.HEADING_TMPL % dict(
                title = saxutils.escape(self.title),
                parameters = ''.join(a_lines),
                description = saxutils.escape(self.description),
                tester= saxutils.escape(self.tester),
            )
            return heading
    
        #生成报告  --Findyou添加注释
        def _generate_report(self, result):
            rows = []
            sortedResult = self.sortResult(result.result)
            for cid, (cls, cls_results) in enumerate(sortedResult):
                # subtotal for a class
                np = nf = ne = 0
                for n,t,o,e in cls_results:
                    if n == 0: np += 1
                    elif n == 1: nf += 1
                    else: ne += 1
    
                # format class description
                if cls.__module__ == "__main__":
                    name = cls.__name__
                else:
                    name = "%s.%s" % (cls.__module__, cls.__name__)
                doc = cls.__doc__ and cls.__doc__.split("
    ")[0] or ""
                desc = doc and '%s: %s' % (name, doc) or name
    
                row = self.REPORT_CLASS_TMPL % dict(
                    style = ne > 0 and 'warning' or nf > 0 and 'danger' or 'success',
                    desc = desc,
                    count = np+nf+ne,
                    Pass = np,
                    fail = nf,
                    error = ne,
                    cid = 'c%s' % (cid+1),
                )
                rows.append(row)
    
                for tid, (n,t,o,e) in enumerate(cls_results):
                    self._generate_report_test(rows, cid, tid, n, t, o, e)
    
            report = self.REPORT_TMPL % dict(
                test_list = ''.join(rows),
                count = str(result.success_count+result.failure_count+result.error_count),
                Pass = str(result.success_count),
                fail = str(result.failure_count),
                error = str(result.error_count),
                passrate =self.passrate,
            )
            return report
    
    
        def _generate_report_test(self, rows, cid, tid, n, t, o, e):
            # e.g. 'pt1.1', 'ft1.1', etc
            has_output = bool(o or e)
            # ID修改点为下划线,支持Bootstrap折叠展开特效 - Findyou v0.8.2.1
            #增加error分类 - Findyou v0.8.2.3
            tid = (n == 0 and 'p' or n == 1 and 'f' or 'e') + 't%s_%s' % (cid + 1, tid + 1)
            name = t.id().split('.')[-1]
            doc = t.shortDescription() or ""
            desc = doc and ('%s: %s' % (name, doc)) or name
            tmpl = has_output and self.REPORT_TEST_WITH_OUTPUT_TMPL or self.REPORT_TEST_NO_OUTPUT_TMPL
    
            # utf-8 支持中文 - Findyou
             # o and e should be byte string because they are collected from stdout and stderr?
            if isinstance(o, str):
                # TODO: some problem with 'string_escape': it escape 
     and mess up formating
                # uo = unicode(o.encode('string_escape'))
                try:
                    uo = o
                except:
                    uo = o.decode('utf-8')
            else:
                uo = o
            if isinstance(e, str):
                # TODO: some problem with 'string_escape': it escape 
     and mess up formating
                # ue = unicode(e.encode('string_escape'))
                try:
                    ue = e
                except:
                    ue = e.decode('utf-8')
            else:
                ue = e
    
            script = self.REPORT_TEST_OUTPUT_TMPL % dict(
                id = tid,
                output = saxutils.escape(uo+ue),
            )
    
            row = tmpl % dict(
                tid = tid,
                Class = (n == 0 and 'hiddenRow' or 'none'),
                style = n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'passCase'),
                desc = desc,
                script = script,
                status = self.STATUS[n],
            )
            rows.append(row)
            if not has_output:
                return
    
        def _generate_ending(self):
            return self.ENDING_TMPL
    
    
    ##############################################################################
    # Facilities for running tests from the command line
    ##############################################################################
    
    # Note: Reuse unittest.TestProgram to launch test. In the future we may
    # build our own launcher to support more specific command line
    # parameters like test title, CSS, etc.
    class TestProgram(unittest.TestProgram):
        """
        A variation of the unittest.TestProgram. Please refer to the base
        class for command line parameters.
        """
        def runTests(self):
            # Pick HTMLTestReportCN as the default test runner.
            # base class's testRunner parameter is not useful because it means
            # we have to instantiate HTMLTestReportCN before we know self.verbosity.
            if self.testRunner is None:
                self.testRunner = HTMLTestReportCN(verbosity=self.verbosity)
            unittest.TestProgram.runTests(self)
    
    main = TestProgram
    
    ##############################################################################
    # Executing this module from the command line
    ##############################################################################
    
    if __name__ == "__main__":
        main(module=None)

    【Config】

    定义配置文件config.ini

    # -*- coding: utf-8 -*-
    [HTTP]
    scheme = http
    baseurl = 127.0.0.1
    port = 5000
    timeout = 10.0
    
    [DATABASE]
    host = 10.181.79.156
    port = 3306
    user = root
    passwd = root
    database = interface
    dbchar = utf8
    table = interface_test
    
    [EMAIL]
    on_off = off
    EMAIL_SERVICE = smtp.qq.com
    EMAIL_PORT = 465
    SENDER_ADDRESS = 364942727@qq.com
    SENDER_PASSWORD = szkaushkeanabcde
    RECEIVER_ADDRESS = 364942727@qq.com

    对读取配置文件config.ini方法的封装

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:readConfig.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 13:58 上午
    @Author:Nobita
    @Version:1.0
    @Desciption:封装读取配置ini文件
    """
    
    import os
    import configparser
    import getpathInfo
    
    
    class ReadConfig():
        def __init__(self):
            self.path = getpathInfo.get_Path()  # 调用实例化
            self.config_path = os.path.join(self.path, 'Config', 'Config.ini')  # 这句话是在path路径下再加一级
            self.config = configparser.ConfigParser()  # 调用外部的读取配置文件的方法
            self.config.read(self.config_path, encoding='utf-8')
    
        def get_http(self, name):
            value = self.config.get('HTTP', name)
            return value
    
        def get_email(self, name):
            value = self.config.get('EMAIL', name)
            return value
    
        def get_mysql(self, name):  # 写好,留以后备用。但是因为我们没有对数据库的操作,所以这个可以屏蔽掉
            value = self.config.get('DATABASE', name)
            return value
    
    
    if __name__ == '__main__':  # 测试一下,我们读取配置文件的方法是否可用
        print('HTTP中的baseurl值为:', ReadConfig().get_http('baseurl'))
        print('EMAIL中的开关on_off值为:', ReadConfig().get_email('on_off'))

    定义接口用例是否执行的配置文件

    learning-API-test/test_login
    learning-API-test/test_header
    #learning-API-test/test_auth
    #learning-API-test/test_menu

    【learning-API-test】

    写了一个用来测试的接口demo,具体代码参考github,这里不做详细介绍。

    【Log】

    文件夹logs用来存储log日志的文件

    【框架流程图】

    存放此接口自动化框架的流程图

    【Report】

    存放生成的html接口测试报告report.html

    【TestCase】

    用来存放各个接口的测试用例。这里我举两个栗子

    /login接口

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:test_login.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 9:28 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:/login接口的测试用例及断言
    """
    
    import json
    import unittest
    import paramunittest
    from Common import readExcel, geturlParams
    from Common.Assert import Assertions
    from Common.Request import RunMain
    
    url = geturlParams.geturlParams().get_Url()  # 调用我们的geturlParams获取我们拼接的URL
    login_xls = readExcel.readExcel().get_xls('learning-API-test_Case.xlsx', 'login')
    
    
    @paramunittest.parametrized(*login_xls)
    class test_learning_API(unittest.TestCase):
    
        def setParameters(self, case_name, path, headers, data, method):
            """
            set params
            :param case_name:
            :param path
            :param headers
            :param data
            :param method
            :return:
            """
            self.case_name = case_name
            self.path = path
            self.headers = headers
            self.data = data
            self.method = method
    
        def description(self):
            """
            test report description
            :return:
            """
            print(self.case_name)
    
        def setUp(self):
            """
    
            :return:
            """
            print("测试开始,测试用例名称:{}".format(self.case_name))
    
        def test_login(self):
            self.checkResult()
    
        def tearDown(self):
            print("测试结束,输出log完结
    
    ")
    
        def checkResult(self):
            """
            check test Log
            :return:
            """
            request_url = url + self.path
            new_data = json.loads(self.data)  # 将Excel中提取的data从字符串转换成字典形式入参
            info = RunMain().run_main(method=self.method, url=request_url,
                                      data=new_data)  # 根据Excel中的method调用run_main来进行requests请求,并拿到响应
            print('接口响应报文:{}'.format(info))  # 在report中打印响应报文
            # 对响应结果进行断言
            if self.case_name == 'login_pass':
                Assertions().assert_code(info['code'], 10200)
                Assertions().assert_in_text(info['message'], 'success')
            if self.case_name == 'login_error':
                Assertions().assert_code(info['code'], 10104)
                Assertions().assert_in_text(info['message'], 'error')
            if self.case_name == 'login_null':
                Assertions().assert_code(info['code'], 10103)
                Assertions().assert_in_text(info['message'], 'null')
    
    
    if __name__ == "__main__":
        # unittest.main()
        pass

    /header接口

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:test_header.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 11:28 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:/headers接口的测试用例及断言
    """
    
    import json
    import unittest
    import paramunittest
    from Common import readExcel, geturlParams
    from Common.Assert import Assertions
    from Common.Request import RunMain
    
    url = geturlParams.geturlParams().get_Url()  # 调用我们的geturlParams获取我们拼接的URL
    login_xls = readExcel.readExcel().get_xls('learning-API-test_Case.xlsx', 'header')
    
    
    @paramunittest.parametrized(*login_xls)
    class test_learning_API(unittest.TestCase):
    
        def setParameters(self, case_name, path, headers, data, method):
            """
            set params
            :param case_name:
            :param path
            :param headers
            :param data
            :param method
            :return:
            """
            self.case_name = case_name
            self.path = path
            self.headers = headers
            self.data = data
            self.method = method
    
        def description(self):
            """
            test report description
            :return:
            """
            print(self.case_name)
    
        def setUp(self):
            """
    
            :return:
            """
            print("测试开始,测试用例名称:{}".format(self.case_name))
    
        def test_header(self):
            self.checkResult()
    
        def tearDown(self):
            print("测试结束,输出log完结
    
    ")
    
        def checkResult(self):
            """
            check test Log
            :return:
            """
            request_url = url + self.path
            headers = self.headers
            new_headers = json.loads(headers)
            info = RunMain().run_main(method=self.method, url=request_url, headers=new_headers
                                      )  # 根据Excel中的method调用run_main来进行requests请求,并拿到响应
            print('接口响应报文:{}'.format(info))  # 在report中打印响应报文
            # 对响应结果进行断言
            if self.case_name == 'header_pass':
                Assertions().assert_code(info['code'], 10200)
                Assertions().assert_in_text(info['message'], 'ok')
    
    
    if __name__ == "__main__":
        # unittest.main()
        pass

    【TestFile】

    用来存放接口项目的测试数据,具体内容参考github上的文件内容。

    【getpathInfo】

    用来获取项目的文件路径,一般都放在工程根目录。

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    """
    @File:getpathInfo.py
    @E-mail:364942727@qq.com
    @Time:2020/9/3 7:58 下午
    @Author:Nobita
    @Version:1.0
    @Desciption:获取项目的文件路径
    """
    
    import os
    
    
    def get_Path():
        path = os.path.split(os.path.realpath(__file__))[0]
        return path
    
    
    if __name__ == '__main__':  # 执行该文件,测试下是否OK
        print('测试路径是否OK,路径为:', get_Path())

    【requirements.txt】

    整个项目所需要的依赖包及精确的版本信息。

    APScheduler==3.6.3
    certifi==2020.6.20
    chardet==3.0.4
    click==7.1.2
    Flask==1.0.2
    idna==2.8
    itsdangerous==1.1.0
    Jinja2==2.11.2
    MarkupSafe==1.1.1
    ParamUnittest==0.2
    pycryptodome==3.7.3
    PyEmail==0.0.1
    pytz==2020.1
    requests==2.22.0
    six==1.15.0
    tzlocal==2.1
    urllib3==1.25.10
    Werkzeug==1.0.1
    xlrd==1.2.0

    【RunAll.py】

    对项目所有功能模块调用的封装。

    import os
    import Common.HTMLTestRunner as HTMLTestRunner
    import getpathInfo
    import unittest
    from Config import readConfig
    from Common.SendEmail import SendEmail
    from Common.Log import logger
    
    send_mail = SendEmail()
    path = getpathInfo.get_Path()
    report_path = os.path.join(path, 'Report')
    resultPath = os.path.join(report_path, "report.html")  # Log/report.html
    on_off = readConfig.ReadConfig().get_email('on_off')
    log = logger
    
    
    class AllTest:  # 定义一个类AllTest
        def __init__(self):  # 初始化一些参数和数
            self.caseListFile = os.path.join(path, "Config", "caselist.txt")  # 配置执行哪些测试文件的配置文件路径
            self.caseFile = os.path.join(path, "TestCase")  # 真正的测试断言文件路径
            self.caseList = []
            log.info('测试报告的路径:{},执行用例配置文件路径:{}'.format(resultPath, self.caseListFile))  # 将文件路径输入到日志,方便定位查看问题
    
        def set_case_list(self):
            """
            读取caselist.txt文件中的用例名称,并添加到caselist元素组
            :return:
            """
            fb = open(self.caseListFile)
            for value in fb.readlines():
                data = str(value)
                if data != '' and not data.startswith("#"):  # 如果data非空且不以#开头
                    self.caseList.append(data.replace("
    ", ""))  # 读取每行数据会将换行转换为
    ,去掉每行数据中的
    
            fb.close()
            log.info('执行的测试用例:{}'.format(self.caseList))
    
        def set_case_suite(self):
            """
    
            :return:
            """
            self.set_case_list()  # 通过set_case_list()拿到caselist元素组
            test_suite = unittest.TestSuite()
            suite_module = []
            for case in self.caseList:  # 从caselist元素组中循环取出case
                case_name = case.split("/")[-1]  # 通过split函数来将aaa/bbb分割字符串,-1取后面,0取前面
                print(case_name + ".py")  # 打印出取出来的名称
                # 批量加载用例,第一个参数为用例存放路径,第一个参数为路径文件名
                discover = unittest.defaultTestLoader.discover(self.caseFile, pattern=case_name + '.py', top_level_dir=None)
                suite_module.append(discover)  # 将discover存入suite_module元素组
                print('suite_module:' + str(suite_module))
            if len(suite_module) > 0:  # 判断suite_module元素组是否存在元素
                for suite in suite_module:  # 如果存在,循环取出元素组内容,命名为suite
                    for test_name in suite:  # 从discover中取出test_name,使用addTest添加到测试集
                        test_suite.addTest(test_name)
            else:
                print('else:')
                return None
            return test_suite  # 返回测试集
    
        def run(self):
            """
            run test
            :return:
            """
            try:
                suit = self.set_case_suite()  # 调用set_case_suite获取test_suite
                if suit is not None:  # 判断test_suite是否为空
                    fp = open(resultPath, 'wb')  # 打开Report/report.html测试报告文件,如果不存在就创建
                    # 调用HTMLTestRunner
                    runner = HTMLTestRunner.HTMLTestReportCN(stream=fp, tester='Shengkai Chen', title='Learning_API 接口测试报告',
                                                             description=None)
                    runner.run(suit)
                else:
                    print("Have no case to test.")
                    log.info('没有可以执行的测试用例,请查看用例配置文件caselist.txt')
            except Exception as ex:
                print(str(ex))
                log.info('{}'.format(str(ex)))
    
            finally:
                print("*********TEST END*********")
            # 判断邮件发送的开关
            if on_off == 'on':
                SendEmail().send_email()
            else:
                print("邮件发送开关配置关闭,请打开开关后可正常自动发送测试报告")
    
    
    if __name__ == '__main__':
        AllTest().run()

    【README.md】

    接口测试框架项目的详细介绍文档。具体内容参考github上的文件内容。

    结束语

    花了一个周末的时间,对以前的框架代码进行了优化,

    更多功能需要结合生产上的业务需求进行开发挖掘。

    学习和工作是一个循序渐进,不断肯定以及不断否定自我的过程。

    希望我们能在此过程中像代码一样迭代自我,加油!

    最后我把这个项目仓库命名为:API_Auto_Test

    附:github下载地址

    作者:Nobita Chen

    -----------------------------------------------

    慢慢的,你总会发现,你的努力没有白费。

  • 相关阅读:
    修改文件小练习
    登录、注册、删除小练习
    自动生成用户名和密码
    自动生成密码文件
    监控日志被攻击情况-小练习
    随机函数_手机自动生成小练习
    as与c++的反射机制对比
    as中的陷阱
    关于as中的事件与回调函数
    身份证号码验证
  • 原文地址:https://www.cnblogs.com/chenshengkai/p/13619525.html
Copyright © 2020-2023  润新知