精华内容
下载资源
问答
  • 完整的框架源码下载...一、Python+unittest+requests+HTMLTestRunner 完整的接口自动化测试框架搭建_00——框架结构简解 首先配置好开发环境,下载安装Python并...

    完整的框架源码下载 https://gitee.com/submi_to/interfaceTest/tree/develop/,欢迎添加我的微信,相互学习探讨~1305618688,qq交流群:849102042

    一、Python+unittest+requests+HTMLTestRunner 完整的接口自动化测试框架搭建_00——框架结构简解

     首先配置好开发环境,下载安装Python并下载安装pycharm,在pycharm中创建项目功能目录。如果不会的可以百度Google一下,该内容网上的讲解还是比较多比较全的!

    大家可以先简单了解下该项目的目录结构介绍,后面会针对每个文件有详细注解和代码。

    common:

    ——configDb.py:这个文件主要编写数据库连接池的相关内容,本项目暂未考虑使用数据库来存储读取数据,此文件可忽略,或者不创建。本人是留着以后如果有相关操作时,方便使用。

    ——configEmail.py:这个文件主要是配置发送邮件的主题、正文等,将测试报告发送并抄送到相关人邮箱的逻辑。

    ——configHttp.py:这个文件主要来通过get、post、put、delete等方法来进行http请求,并拿到请求响应。

    ——HTMLTestRunner.py:主要是生成测试报告相关

    ——Log.py:调用该类的方法,用来打印生成日志

    result:

    ——logs:生成的日志文件

    ——report.html:生成的测试报告

    testCase:

    ——test01case.py:读取userCase.xlsx中的用例,使用unittest来进行断言校验

    testFile/case:

    ——userCase.xlsx:对下面test_api.py接口服务里的接口,设计了三条简单的测试用例,如参数为null,参数不正确等

    caselist.txt:配置将要执行testCase目录下的哪些用例文件,前加#代表不进行执行。当项目过于庞大,用例足够多的时候,我们可以通过这个开关,来确定本次执行哪些接口的哪些用例。

    config.ini:数据库、邮箱、接口等的配置项,用于方便的调用读取。

    getpathInfo.py:获取项目绝对路径

    geturlParams.py:获取接口的URL、参数、method等

    readConfig.py:读取配置文件的方法,并返回文件中内容

    readExcel.py:读取Excel的方法

    runAll.py:开始执行接口自动化,项目工程部署完毕后直接运行该文件即可

    test_api.py:自己写的提供本地测试的接口服务

    test_sql.py:测试数据库连接池的文件,本次项目未用到数据库,可以忽略

    二、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_01——测试接口服务

    首先,我们想搭建一个接口自动化测试框架,前提我们必须要有一个可支持测试的接口服务。有人可能会说,现在我们的环境不管测试环境,还是生产环境有现成的接口。但是,一般工作环境中的接口,不太满足我们框架的各种条件。举例如,接口a可能是get接口b可能又是post,等等等等。因此我决定自己写一个简单的接口!用于我们这个框架的测试!

    按第一讲的目录创建好文件,打开test_api.py,写入如下代码

    import flask
    import json
    from flask import request
    
    '''
    flask: web框架,通过flask提供的装饰器@server.route()将普通函数转换为服
    '''
    # 创建一个服务,把当前这个python文件当做一个服务
    server = flask.Flask(__name__)
    # @server.route()可以将普通函数转变为服务 登录接口的路径、请求方式
    @server.route('/login', methods=['get', 'post'])
    def login():
        # 获取通过url请求传参的数据
        username = request.values.get('name')
        # 获取url请求传的密码,明文
        pwd = request.values.get('pwd')
        # 判断用户名、密码都不为空
        if username and pwd:
            if username == 'xiaoming' and pwd == '111':
                resu = {'code': 200, 'message': '登录成功'}
                return json.dumps(resu, ensure_ascii=False)  # 将字典转换字符串
            else:
                resu = {'code': -1, 'message': '账号密码错误'}
                return json.dumps(resu, ensure_ascii=False)
        else:
            resu = {'code': 10001, 'message': '参数不能为空!'}
            return json.dumps(resu, ensure_ascii=False)
    
    if __name__ == '__main__':
        server.run(debug=True, port=8888, host='127.0.0.1')
    
    

    执行test_api.py,在浏览器中输入http://127.0.0.1:8888/login?name=xiaoming&pwd=11199回车,验证我们的接口服务是否正常~

    变更我们的参数,查看不同的响应结果确认接口服务一切正常

    三、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_02——配置文件读取

    在我们第二讲中,我们已经通过flask这个web框架创建好了我们用于测试的接口服务,因此我们可以把这个接口抽出来一些参数放到配置文件,然后通过一个读取配置文件的方法,方便后续的使用。同样还有邮件的相关配置~

    按第一讲的目录创建好config.ini文件,打开该文件写入如下:

    # -*- coding: utf-8 -*-
    [HTTP]
    scheme = http
    baseurl = 127.0.0.1
    port = 8888
    timeout = 10.0
    
    
    
    [EMAIL]
    on_off = on;
    subject = 接口自动化测试报告
    app = Outlook
    addressee = songxiaobao@qq.com
    cc = zhaobenshan@qq.com

    在HTTP中,协议http,baseURL,端口,超时时间。

    在邮件中on_off是设置的一个开关,=on打开,发送邮件,=其他不发送邮件。subject邮件主题,addressee收件人,cc抄送人。

    在我们编写readConfig.py文件前,我们先写一个获取项目某路径下某文件绝对路径的一个方法。按第一讲的目录结构创建好getpathInfo.py,打开该文件

    import os
    
    def get_Path():
        path = os.path.split(os.path.realpath(__file__))[0]
        return path
    
    if __name__ == '__main__':# 执行该文件,测试下是否OK
        print('测试路径是否OK,路径为:', get_Path())
    

    填写如上代码并执行后,查看输出结果,打印出了该项目的绝对路径:

    继续往下走,同理,按第一讲目录创建好readConfig.py文件,打开该文件,以后的章节不在累赘

    import os
    import configparser
    import getpathInfo#引入我们自己的写的获取路径的类
    
    path = getpathInfo.get_Path()#调用实例化,还记得这个类返回的路径为C:\Users\songlihui\PycharmProjects\dkxinterfaceTest
    config_path = os.path.join(path, 'config.ini')#这句话是在path路径下再加一级,最后变成C:\Users\songlihui\PycharmProjects\dkxinterfaceTest\config.ini
    config = configparser.ConfigParser()#调用外部的读取配置文件的方法
    config.read(config_path, encoding='utf-8')
    
    class ReadConfig():
    
        def get_http(self, name):
            value = config.get('HTTP', name)
            return value
        def get_email(self, name):
            value = config.get('EMAIL', name)
            return value
        def get_mysql(self, name):#写好,留以后备用。但是因为我们没有对数据库的操作,所以这个可以屏蔽掉
            value = 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'))

    执行下readConfig.py,查看数据是否正确

    一切OK

    四、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_03——读取Excel中的case

    配置文件写好了,接口我们也有了,然后我们来根据我们的接口设计我们简单的几条用例。首先在前两讲中我们写了一个我们测试的接口服务,针对这个接口服务存在三种情况的校验。正确的用户名和密码,账号密码错误和账号密码为空

    我们根据上面的三种情况,将对这个接口的用例写在一个对应的单独文件中testFile\case\userCase.xlsx ,userCase.xlsx内容如下:

    紧接着,我们有了用例设计的Excel了,我们要对这个Excel进行数据的读取操作,继续往下,我们创建readExcel.py文件

    import os
    import getpathInfo# 自己定义的内部类,该类返回项目的绝对路径
    #调用读Excel的第三方库xlrd
    from xlrd import open_workbook
    # 拿到该项目所在的绝对路径
    path = getpathInfo.get_Path()
    
    class readExcel():
        def get_xls(self, xls_name, sheet_name):# xls_name填写用例的Excel名称 sheet_name该Excel的sheet名称
            cls = []
            # 获取用例文件路径
            xlsPath = os.path.join(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('userCase.xlsx', 'login'))
        print(readExcel().get_xls('userCase.xlsx', 'login')[0][1])
        print(readExcel().get_xls('userCase.xlsx', 'login')[1][2])

    结果为:

    完全正确~

    五、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_04——requests请求

    配置文件有了,读取配置文件有了,用例有了,读取用例有了,我们的接口服务有了,我们是不是该写对某个接口进行http请求了,这时候我们需要使用pip install requests来安装第三方库,在common下configHttp.py,configHttp.py的内容如下:

    import requests
    import json
    
    
    class RunMain():
    
        def send_post(self, url, data):  # 定义一个方法,传入需要的参数url和data
            # 参数必须按照url、data顺序传入
            result = requests.post(url=url, data=data).json()  # 因为这里要封装post方法,所以这里的url和data值不能写死
            res = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2)
            return res
    
        def send_get(self, url, data):
            result = requests.get(url=url, params=data).json()
            res = json.dumps(result, ensure_ascii=False, sort_keys=True, indent=2)
            return res
    
        def run_main(self, method, url=None, data=None):  # 定义一个run_main函数,通过传过来的method来进行不同的get或post请求
            result = None
            if method == 'post':
                result = self.send_post(url, data)
            elif method == 'get':
                result = self.send_get(url, data)
            else:
                print("method值错误!!!")
            return result
    
    
    if __name__ == '__main__':  # 通过写死参数,来验证我们写的请求是否正确
        result1 = RunMain().run_main('post', 'http://127.0.0.1:8888/login', {'name': 'xiaoming','pwd':'111'})
        result2 = RunMain().run_main('get', 'http://127.0.0.1:8888/login', 'name=xiaoming&pwd=111')
        print(result1)
        print(result2)

    执行该文件,验证结果正确性:

    我们发现和浏览器中进行请求该接口,得到的结果一致,说明没有问题,一切OK

    六、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_05——参数动态化

    在上一讲中,我们写了针对我们的接口服务,设计的三种测试用例,使用写死的参数(result = RunMain().run_main('post', 'http://127.0.0.1:8888/login', 'name=xiaoming&pwd='))来进行requests请求。本讲中我们写一个类,来用于分别获取这些参数,来第一讲的目录创建geturlParams.py,geturlParams.py文件中的内容如下:

    import readConfig as readConfig
    
    readconfig = readConfig.ReadConfig()
    
    class geturlParams():# 定义一个方法,将从配置文件中读取的进行拼接
        def get_Url(self):
            new_url = readconfig.get_http('scheme') + '://' + readconfig.get_http('baseurl') + ':8888' + '/login' + '?'
            #logger.info('new_url'+new_url)
            return new_url
    
    if __name__ == '__main__':# 验证拼接后的正确性
        print(geturlParams().get_Url())

    通过将配置文件中的进行拼接,拼接后的结果:http://127.0.0.1:8888/login?和我们请求的一致

    七、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_06——unittest断言

    以上的我们都准备好了,剩下的该写我们的unittest断言测试case了,在testCase下创建test01case.py文件,文件中内容如下:

    import json
    import unittest
    from common.configHttp import RunMain
    import paramunittest
    import geturlParams
    import urllib.parse
    # import pythoncom
    import readExcel
    # pythoncom.CoInitialize()
    
    url = geturlParams.geturlParams().get_Url()# 调用我们的geturlParams获取我们拼接的URL
    login_xls = readExcel.readExcel().get_xls('userCase.xlsx', 'login')
    
    @paramunittest.parametrized(*login_xls)
    class testUserLogin(unittest.TestCase):
        def setParameters(self, case_name, path, query, method):
            """
            set params
            :param case_name:
            :param path
            :param query
            :param method
            :return:
            """
            self.case_name = str(case_name)
            self.path = str(path)
            self.query = str(query)
            self.method = str(method)
    
        def description(self):
            """
            test report description
            :return:
            """
            self.case_name
    
        def setUp(self):
            """
    
            :return:
            """
            print(self.case_name+"测试开始前准备")
    
        def test01case(self):
            self.checkResult()
    
        def tearDown(self):
            print("测试结束,输出log完结\n\n")
    
        def checkResult(self):# 断言
            """
            check test result
            :return:
            """
            url1 = "http://www.xxx.com/login?"
            new_url = url1 + self.query
            data1 = dict(urllib.parse.parse_qsl(urllib.parse.urlsplit(new_url).query))# 将一个完整的URL中的name=&pwd=转换为{'name':'xxx','pwd':'bbb'}
            info = RunMain().run_main(self.method, url, data1)# 根据Excel中的method调用run_main来进行requests请求,并拿到响应
            ss = json.loads(info)# 将响应转换为字典格式
            if self.case_name == 'login':# 如果case_name是login,说明合法,返回的code应该为200
                self.assertEqual(ss['code'], 200)
            if self.case_name == 'login_error':# 同上
                self.assertEqual(ss['code'], -1)
            if self.case_name == 'login_null':# 同上
                self.assertEqual(ss['code'], 10001)

    八、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_07——HTMLTestRunner

    按我的目录结构,在common下创建HTMLTestRunner.py文件,内容如下:

    # -*- coding: utf-8 -*-
    """
    A TestRunner for use with the Python unit testing framework. It
    generates a HTML report to show the result at a glance.
    The simplest way to use this is to invoke its main method. E.g.
        import unittest
        import HTMLTestRunner
        ... define your tests ...
        if __name__ == '__main__':
            HTMLTestRunner.main()
    For more customization options, instantiates a HTMLTestRunner object.
    HTMLTestRunner is a counterpart to unittest's TextTestRunner. E.g.
        # output to a file
        fp = file('my_report.html', 'wb')
        runner = HTMLTestRunner.HTMLTestRunner(
                    stream=fp,
                    title='My unit test',
                    description='This demonstrates the report output by HTMLTestRunner.'
                    )
        # 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
    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"
    __version__ = "0.9.1"
    
    """
    Change History
    Version 0.9.1
    * 用Echarts添加执行情况统计图 (灰蓝)
    Version 0.9.0
    * 改成Python 3.x (灰蓝)
    Version 0.8.3
    * 使用 Bootstrap稍加美化 (灰蓝)
    * 改为中文 (灰蓝)
    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
    import sys
    import io
    import time
    import unittest
    from xml.sax import saxutils
    
    
    # ------------------------------------------------------------------------
    # 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: u'通过',
            1: u'失败',
            2: u'错误',
        }
    
        DEFAULT_TITLE = 'Unit Test Report'
        DEFAULT_DESCRIPTION = ''
    
        # ------------------------------------------------------------------------
        # 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://cdn.bootcss.com/bootstrap/3.3.0/css/bootstrap.min.css" rel="stylesheet">
        <script src="https://cdn.bootcss.com/echarts/3.8.5/echarts.common.min.js"></script>
        <!-- <script type="text/javascript" src="js/echarts.common.min.js"></script> -->
    
        %(stylesheet)s
    
    </head>
    <body>
        <script language="javascript" type="text/javascript"><!--
        output_list = Array();
        /* level - 0:Summary; 1:Failed; 2:All */
        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 < 1) {
                        tr.className = 'hiddenRow';
                    }
                    else {
                        tr.className = '';
                    }
                }
                if (id.substr(0,2) == 'pt') {
                    if (level > 1) {
                        tr.className = '';
                    }
                    else {
                        tr.className = 'hiddenRow';
                    }
                }
            }
        }
        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);
                }
                id_list[i] = tid;
                if (tr.className) {
                    toHide = 0;
                }
            }
            for (var i = 0; i < count; i++) {
                tid = id_list[i];
                if (toHide) {
                    document.getElementById('div_'+tid).style.display = 'none'
                    document.getElementById(tid).className = 'hiddenRow';
                }
                else {
                    document.getElementById(tid).className = '';
                }
            }
        }
        function showTestDetail(div_id){
            var details_div = document.getElementById(div_id)
            var displayState = details_div.style.display
            // alert(displayState)
            if (displayState != 'block' ) {
                displayState = 'block'
                details_div.style.display = 'block'
            }
            else {
                details_div.style.display = 'none'
            }
        }
        function html_escape(s) {
            s = s.replace(/&/g,'&amp;');
            s = s.replace(/</g,'&lt;');
            s = s.replace(/>/g,'&gt;');
            return s;
        }
        /* obsoleted by detail in <div>
        function showOutput(id, name) {
            var w = window.open("", //url
                            name,
                            "resizable,scrollbars,status,width=800,height=450");
            d = w.document;
            d.write("<pre>");
            d.write(html_escape(output_list[id]));
            d.write("\n");
            d.write("<a href='javascript:window.close()'>close</a>\n");
            d.write("</pre>\n");
            d.close();
        }
        */
        --></script>
        <div id="div_base">
            %(heading)s
            %(report)s
            %(ending)s
            %(chart_script)s
        </div>
    </body>
    </html>
    """  # variables: (title, generator, stylesheet, heading, report, ending, chart_script)
    
        ECHARTS_SCRIPT = """
        <script type="text/javascript">
            // 基于准备好的dom,初始化echarts实例
            var myChart = echarts.init(document.getElementById('chart'));
            // 指定图表的配置项和数据
            var option = {
                title : {
                    text: '测试执行情况',
                    x:'center'
                },
                tooltip : {
                    trigger: 'item',
                    formatter: "{a} <br/>{b} : {c} ({d}%%)"
                },
                color: ['#95b75d', 'grey', '#b64645'],
                legend: {
                    orient: 'vertical',
                    left: 'left',
                    data: ['通过','失败','错误']
                },
                series : [
                    {
                        name: '测试执行情况',
                        type: 'pie',
                        radius : '60%%',
                        center: ['50%%', '60%%'],
                        data:[
                            {value:%(Pass)s, name:'通过'},
                            {value:%(fail)s, name:'失败'},
                            {value:%(error)s, name:'错误'}
                        ],
                        itemStyle: {
                            emphasis: {
                                shadowBlur: 10,
                                shadowOffsetX: 0,
                                shadowColor: 'rgba(0, 0, 0, 0.5)'
                            }
                        }
                    }
                ]
            };
            // 使用刚指定的配置项和数据显示图表。
            myChart.setOption(option);
        </script>
        """  # variables: (Pass, fail, error)
    
        # ------------------------------------------------------------------------
        # 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,Consolas,arial,sans-serif; font-size: 80%; }
        table       { font-size: 100%; }
        pre         { white-space: pre-wrap;word-wrap: break-word; }
        /* -- heading ---------------------------------------------------------------------- */
        h1 {
            font-size: 16pt;
            color: gray;
        }
        .heading {
            margin-top: 0ex;
            margin-bottom: 1ex;
        }
        .heading .attribute {
            margin-top: 1ex;
            margin-bottom: 0;
        }
        .heading .description {
            margin-top: 2ex;
            margin-bottom: 3ex;
        }
        /* -- css div popup ------------------------------------------------------------------------ */
        a.popup_link {
        }
        a.popup_link:hover {
            color: red;
        }
        .popup_window {
            display: none;
            position: relative;
            left: 0px;
            top: 0px;
            /*border: solid #627173 1px; */
            padding: 10px;
            /*background-color: #E6E6D6; */
            font-family: "Lucida Console", "Courier New", Courier, monospace;
            text-align: left;
            font-size: 8pt;
            /* width: 500px;*/
        }
        }
        /* -- report ------------------------------------------------------------------------ */
        #show_detail_line {
            margin-top: 3ex;
            margin-bottom: 1ex;
        }
        #result_table {
            width: 99%;
        }
        #header_row {
            font-weight: bold;
            color: #303641;
            background-color: #ebebeb;
        }
        #total_row  { font-weight: bold; }
        .passClass  { background-color: #bdedbc; }
        .failClass  { background-color: #ffefa4; }
        .errorClass { background-color: #ffc9c9; }
        .passCase   { color: #6c6; }
        .failCase   { color: #FF6600; font-weight: bold; }
        .errorCase  { color: #c00; font-weight: bold; }
        .hiddenRow  { display: none; }
        .testcase   { margin-left: 2em; }
        /* -- ending ---------------------------------------------------------------------- */
        #ending {
        }
        #div_base {
                    position:absolute;
                    top:0%;
                    left:5%;
                    right:5%;
                    width: auto;
                    height: auto;
                    margin: -15px 0 0 0;
        }
    </style>
    """
    
        # ------------------------------------------------------------------------
        # Heading
        #
    
        HEADING_TMPL = """
        <div class='page-header'>
            <h1>%(title)s</h1>
        %(parameters)s
        </div>
        <div style="float: left;width:50%%;"><p class='description'>%(description)s</p></div>
        <div id="chart" style="width:50%%;height:400px;float:left;"></div>
    """  # variables: (title, parameters, description)
    
        HEADING_ATTRIBUTE_TMPL = """<p class='attribute'><strong>%(name)s:</strong> %(value)s</p>
    """  # variables: (name, value)
    
        # ------------------------------------------------------------------------
        # Report
        #
    
        REPORT_TMPL = u"""
        <div class="btn-group btn-group-sm">
            <button class="btn btn-default" onclick='javascript:showCase(0)'>总结</button>
            <button class="btn btn-default" onclick='javascript:showCase(1)'>失败</button>
            <button class="btn btn-default" onclick='javascript:showCase(2)'>全部</button>
        </div>
        <p></p>
        <table id='result_table' class="table table-bordered">
            <colgroup>
                <col align='left' />
                <col align='right' />
                <col align='right' />
                <col align='right' />
                <col align='right' />
                <col align='right' />
            </colgroup>
            <tr id='header_row'>
                <td>测试套件/测试用例</td>
                <td>总数</td>
                <td>通过</td>
                <td>失败</td>
                <td>错误</td>
                <td>查看</td>
            </tr>
            %(test_list)s
            <tr id='total_row'>
                <td>总计</td>
                <td>%(count)s</td>
                <td>%(Pass)s</td>
                <td>%(fail)s</td>
                <td>%(error)s</td>
                <td>&nbsp;</td>
            </tr>
        </table>
    """  # variables: (test_list, count, Pass, fail, error)
    
        REPORT_CLASS_TMPL = u"""
        <tr class='%(style)s'>
            <td>%(desc)s</td>
            <td>%(count)s</td>
            <td>%(Pass)s</td>
            <td>%(fail)s</td>
            <td>%(error)s</td>
            <td><a href="javascript:showClassDetail('%(cid)s',%(count)s)">详情</a></td>
        </tr>
    """  # variables: (style, desc, count, Pass, fail, error, cid)
    
        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'>
        <!--css div popup start-->
        <a class="popup_link" onfocus='this.blur();' href="javascript:showTestDetail('div_%(tid)s')" >
            %(status)s</a>
        <div id='div_%(tid)s' class="popup_window">
            <pre>%(script)s</pre>
        </div>
        <!--css div popup end-->
        </td>
    </tr>
    """  # variables: (tid, Class, style, desc, status)
    
        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'>%(status)s</td>
    </tr>
    """  # variables: (tid, Class, style, desc, status)
    
        REPORT_TEST_OUTPUT_TMPL = r"""%(id)s: %(output)s"""  # variables: (id, output)
    
        # ------------------------------------------------------------------------
        # ENDING
        #
    
        ENDING_TMPL = """<div id='ending'>&nbsp;</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
    
            # result is a list of result in 4 tuple
            # (
            #   result code (0: success; 1: fail; 2: error),
            #   TestCase object,
            #   Test output (byte string),
            #   stack trace,
            # )
            self.result = []
            self.subtestlist = []
    
        def startTest(self, test):
            TestResult.startTest(self, test)
            # just one buffer for both stdout and stderr
            self.outputBuffer = io.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):
            if test not in self.subtestlist:
                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('\n')
                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('\n')
            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('\n')
            else:
                sys.stderr.write('F')
    
        def addSubTest(self, test, subtest, err):
            if err is not None:
                if getattr(self, 'failfast', False):
                    self.stop()
                if issubclass(err[0], test.failureException):
                    self.failure_count += 1
                    errors = self.failures
                    errors.append((subtest, self._exc_info_to_string(err, subtest)))
                    output = self.complete_output()
                    self.result.append((1, test, output + '\nSubTestCase Failed:\n' + str(subtest),
                                        self._exc_info_to_string(err, subtest)))
                    if self.verbosity > 1:
                        sys.stderr.write('F  ')
                        sys.stderr.write(str(subtest))
                        sys.stderr.write('\n')
                    else:
                        sys.stderr.write('F')
                else:
                    self.error_count += 1
                    errors = self.errors
                    errors.append((subtest, self._exc_info_to_string(err, subtest)))
                    output = self.complete_output()
                    self.result.append(
                        (2, test, output + '\nSubTestCase Error:\n' + str(subtest), self._exc_info_to_string(err, subtest)))
                    if self.verbosity > 1:
                        sys.stderr.write('E  ')
                        sys.stderr.write(str(subtest))
                        sys.stderr.write('\n')
                    else:
                        sys.stderr.write('E')
                self._mirrorOutput = True
            else:
                self.subtestlist.append(subtest)
                self.subtestlist.append(test)
                self.success_count += 1
                output = self.complete_output()
                self.result.append((0, test, output + '\nSubTestCase Pass:\n' + str(subtest), ''))
                if self.verbosity > 1:
                    sys.stderr.write('ok ')
                    sys.stderr.write(str(subtest))
                    sys.stderr.write('\n')
                else:
                    sys.stderr.write('.')
    
    
    class HTMLTestRunner(Template_mixin):
    
        def __init__(self, stream=sys.stdout, verbosity=1, title=None, description=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
    
            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('\nTime Elapsed: %s' % (self.stopTime - self.startTime), file=sys.stderr)
            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 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
    
        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 = []
            if result.success_count: status.append(u'通过 %s' % result.success_count)
            if result.failure_count: status.append(u'失败 %s' % result.failure_count)
            if result.error_count:   status.append(u'错误 %s' % result.error_count)
            if status:
                status = ' '.join(status)
            else:
                status = 'none'
            return [
                (u'开始时间', startTime),
                (u'运行时长', duration),
                (u'状态', status),
            ]
    
        def generateReport(self, test, result):
            report_attrs = self.getReportAttributes(result)
            generator = 'HTMLTestRunner %s' % __version__
            stylesheet = self._generate_stylesheet()
            heading = self._generate_heading(report_attrs)
            report = self._generate_report(result)
            ending = self._generate_ending()
            chart = self._generate_chart(result)
            output = self.HTML_TMPL % dict(
                title=saxutils.escape(self.title),
                generator=generator,
                stylesheet=stylesheet,
                heading=heading,
                report=report,
                ending=ending,
                chart_script=chart
            )
            self.stream.write(output.encode('utf8'))
    
        def _generate_stylesheet(self):
            return self.STYLESHEET_TMPL
    
        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),
            )
            return heading
    
        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("\n")[0] or ""
                desc = doc and '%s: %s' % (name, doc) or name
    
                row = self.REPORT_CLASS_TMPL % dict(
                    style=ne > 0 and 'errorClass' or nf > 0 and 'failClass' or 'passClass',
                    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),
            )
            return report
    
        def _generate_chart(self, result):
            chart = self.ECHARTS_SCRIPT % dict(
                Pass=str(result.success_count),
                fail=str(result.failure_count),
                error=str(result.error_count),
            )
            return chart
    
        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)
            tid = (n == 0 and 'p' or 'f') + '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
    
            script = self.REPORT_TEST_OUTPUT_TMPL % dict(
                id=tid,
                output=saxutils.escape(o + e),
            )
    
            row = tmpl % dict(
                tid=tid,
                Class=(n == 0 and 'hiddenRow' or 'none'),
                style=(n == 2 and 'errorCase' or (n == 1 and 'failCase' or 'none')),
                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 HTMLTestRunner as the default test runner.
            # base class's testRunner parameter is not useful because it means
            # we have to instantiate HTMLTestRunner before we know self.verbosity.
            if self.testRunner is None:
                self.testRunner = HTMLTestRunner(verbosity=self.verbosity)
            unittest.TestProgram.runTests(self)
    
    
    main = TestProgram
    
    ##############################################################################
    # Executing this module from the command line
    ##############################################################################
    
    if __name__ == "__main__":
        main(module=None)
    

    九、Python+unittest+requests+HTMLTestRunner完整的接口自动化测试框架搭建_08——调用生成测试报告

    先别急着创建runAll.py文件(所有工作做完,最后我们运行runAll.py文件来执行接口自动化的测试工作并生成测试报告发送报告到相关人邮箱),但是我们在创建此文件前,还缺少点东东。按我的目录结构创建caselist.txt文件,内容如下:

    user/test01case
    #user/test02case
    #user/test03case
    #user/test04case
    #user/test05case
    #shop/test_shop_list
    #shop/test_my_shop
    #shop/test_new_shop

    这个文件的作用是,我们通过这个文件来控制,执行哪些模块下的哪些unittest用例文件。如在实际的项目中:user模块下的test01case.py,店铺shop模块下的我的店铺my_shop,如果本轮无需执行哪些模块的用例的话,就在前面添加#。我们继续往下走,还缺少一个发送邮件的文件。在common下创建configEmail.py文件,内容如下:

    # import os
    # import win32com.client as win32
    # import datetime
    # import readConfig
    # import getpathInfo
    # 
    # 
    # read_conf = readConfig.ReadConfig()
    # subject = read_conf.get_email('subject')#从配置文件中读取,邮件主题
    # app = str(read_conf.get_email('app'))#从配置文件中读取,邮件类型
    # addressee = read_conf.get_email('addressee')#从配置文件中读取,邮件收件人
    # cc = read_conf.get_email('cc')#从配置文件中读取,邮件抄送人
    # mail_path = os.path.join(getpathInfo.get_Path(), 'result', 'report.html')#获取测试报告路径
    # 
    # class send_email():
    #     def outlook(self):
    #         olook = win32.Dispatch("%s.Application" % app)
    #         mail = olook.CreateItem(win32.constants.olMailItem)
    #         mail.To = addressee # 收件人
    #         mail.CC = cc # 抄送
    #         mail.Subject = str(datetime.datetime.now())[0:19]+'%s' %subject#邮件主题
    #         mail.Attachments.Add(mail_path, 1, 1, "myFile")
    #         content = """
    #                     执行测试中……
    #                     测试已完成!!
    #                     生成报告中……
    #                     报告已生成……
    #                     报告已邮件发送!!
    #                     """
    #         mail.Body = content
    #         mail.Send()
    # 
    # 
    # if __name__ == '__main__':# 运营此文件来验证写的send_email是否正确
    #     print(subject)
    #     send_email().outlook()
    #     print("send email ok!!!!!!!!!!")
    
    
    # 两种方式,第一种是用的win32com,因为系统等各方面原因,反馈win32问题较多,建议改成下面的smtplib方式
    import os
    import smtplib
    import base64
    from email.mime.text import MIMEText
    from email.mime.multipart import MIMEMultipart
    
    
    class SendEmail(object):
        def __init__(self, username, passwd, recv, title, content,
                     file=None, ssl=False,
                     email_host='smtp.163.com', port=25, ssl_port=465):
            self.username = username  # 用户名
            self.passwd = passwd  # 密码
            self.recv = recv  # 收件人,多个要传list ['a@qq.com','b@qq.com]
            self.title = title  # 邮件标题
            self.content = content  # 邮件正文
            self.file = file  # 附件路径,如果不在当前目录下,要写绝对路径
            self.email_host = email_host  # smtp服务器地址
            self.port = port  # 普通端口
            self.ssl = ssl  # 是否安全链接
            self.ssl_port = ssl_port  # 安全链接端口
    
        def send_email(self):
            msg = MIMEMultipart()
            # 发送内容的对象
            if self.file:  # 处理附件的
                file_name = os.path.split(self.file)[-1]  # 只取文件名,不取路径
                try:
                    f = open(self.file, 'rb').read()
                except Exception as e:
                    raise Exception('附件打不开!!!!')
                else:
                    att = MIMEText(f, "base64", "utf-8")
                    att["Content-Type"] = 'application/octet-stream'
                    # base64.b64encode(file_name.encode()).decode()
                    new_file_name = '=?utf-8?b?' + base64.b64encode(file_name.encode()).decode() + '?='
                    # 这里是处理文件名为中文名的,必须这么写
                    att["Content-Disposition"] = 'attachment; filename="%s"' % (new_file_name)
                    msg.attach(att)
            msg.attach(MIMEText(self.content))  # 邮件正文的内容
            msg['Subject'] = self.title  # 邮件主题
            msg['From'] = self.username  # 发送者账号
            msg['To'] = ','.join(self.recv)  # 接收者账号列表
            if self.ssl:
                self.smtp = smtplib.SMTP_SSL(self.email_host, port=self.ssl_port)
            else:
                self.smtp = smtplib.SMTP(self.email_host, port=self.port)
            # 发送邮件服务器的对象
            self.smtp.login(self.username, self.passwd)
            try:
                self.smtp.sendmail(self.username, self.recv, msg.as_string())
                pass
            except Exception as e:
                print('出错了。。', e)
            else:
                print('发送成功!')
            self.smtp.quit()
    
    
    if __name__ == '__main__':
    
        m = SendEmail(
            username='@163.com',
            passwd='',
            recv=[''],
            title='',
            content='测试发送邮件',
            file=r'E:\test_record\v2.3.3\测试截图\调整样式.png',
            ssl=True,
        )
        m.send_email()

    运行configEmail.py验证邮件发送是否正确

    邮件已发送成功,我们进入到邮箱中进行查看,一切OK~~不过这我要说明一下,我写的send_email是调用的outlook,如果您的电脑本地是使用的其他邮件服务器的话,这块的代码需要修改为您想使用的邮箱调用代码

    如果遇到发送的多个收件人,但是只有第一个收件人可以收到邮件,或者收件人为空可以参考http://www.361way.com/smtplib-multiple-addresses/5503.html

    继续往下走,这下我们该创建我们的runAll.py文件了

    import os
    import common.HTMLTestRunner as HTMLTestRunner
    import getpathInfo
    import unittest
    import readConfig
    from common.configEmail import SendEmail
    from apscheduler.schedulers.blocking import BlockingScheduler
    import pythoncom
    # import common.Log
    
    send_mail = SendEmail(
            username='@163.com',
            passwd='',
            recv=[''],
            title='',
            content='测试发送邮件',
            file=r'E:\test_record\v2.3.3\测试截图\调整样式.png',
            ssl=True,
        )
    path = getpathInfo.get_Path()
    report_path = os.path.join(path, 'result')
    on_off = readConfig.ReadConfig().get_email('on_off')
    # log = common.Log.logger
    
    class AllTest:#定义一个类AllTest
        def __init__(self):#初始化一些参数和数据
            global resultPath
            resultPath = os.path.join(report_path, "report.html")#result/report.html
            self.caseListFile = os.path.join(path, "caselist.txt")#配置执行哪些测试文件的配置文件路径
            self.caseFile = os.path.join(path, "testCase")#真正的测试断言文件路径
            self.caseList = []
    
        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("\n", ""))#读取每行数据会将换行转换为\n,去掉每行数据中的\n
            fb.close()
    
        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
                print('try')
                print(str(suit))
                if suit is not None:#判断test_suite是否为空
                    print('if-suit')
                    fp = open(resultPath, 'wb')#打开result/20181108/report.html测试报告文件,如果不存在就创建
                    #调用HTMLTestRunner
                    runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='Test Report', description='Test Description')
                    runner.run(suit)
                else:
                    print("Have no case to test.")
            except Exception as ex:
                print(str(ex))
                #log.info(str(ex))
    
            finally:
                print("*********TEST END*********")
                #log.info("*********TEST END*********")
                fp.close()
            #判断邮件发送的开关
            if on_off == 'on':
                send_mail.send_email()
            else:
                print("邮件发送开关配置关闭,请打开开关后可正常自动发送测试报告")
    # pythoncom.CoInitialize()
    # scheduler = BlockingScheduler()
    # scheduler.add_job(AllTest().run, 'cron', day_of_week='1-5', hour=14, minute=59)
    # scheduler.start()
    
    if __name__ == '__main__':
        AllTest().run()
    
    
    

    执行runAll.py,进到邮箱中查看发送的测试结果报告,打开查看

    然后继续,我们框架到这里就算基本搭建好了,但是缺少日志的输出,在一些关键的参数调用的地方我们来输出一些日志。从而更方便的来维护和查找问题。

    按目录结构继续在common下创建Log.py,内容如下:

    import os
    import logging
    from logging.handlers import TimedRotatingFileHandler
    import getpathInfo
    
    path = getpathInfo.get_Path()
    log_path = os.path.join(path, 'result')  # 存放log文件的路径
    
    
    class Logger(object):
        def __init__(self, logger_name='logs…'):
            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()

    然后我们在需要我们输出日志的地方添加日志:

    我们修改runAll.py文件,在顶部增加import common.Log,然后增加标红框的代码

    让我们再来运行一下runAll.py文件,发现在result下多了一个logs文件,我们打开看一下有没有我们打印的日志

    OK,至此我们的接口自动化测试的框架就搭建完了,后续我们可以将此框架进行进一步优化改造,使用我们真实项目的接口,结合持续集成定时任务等,让这个项目每天定时的来跑啦~~~

    欢迎添加我的微信,相互学习探讨~1305618688,qq交流群:849102042

    2020年9月23追加

    一、、最近有太多人反馈,执行通过后report.html文件中内容为空,这个基本上多数原因是因为用例执行异常报错,导致没有成功执行用例,所以没有生成数据。大家可以运行testCase下的test01Case.py等用例文件,看是不是运行报错了。如果运行成功,再去执行runAll试一下

    展开全文
  • Python测试框架之pytest详解

    万次阅读 多人点赞 2019-08-09 13:16:41
    Python测试框架之前一直用的是unittest+HTMLTestRunner,听到有人说pytest很好用,所以这段时间就看了看pytest文档,在这里做个记录。 官方文档介绍: Pytest is a framework that makes building simple and ...

    前言

    Python测试框架之前一直用的是unittest+HTMLTestRunner,听到有人说pytest很好用,所以这段时间就看了看pytest文档,在这里做个记录。

    官方文档介绍:

    Pytest is a framework that makes building simple and scalable tests easy. Tests are expressive and readable—no boilerplate code required. Get started in minutes with a small unit test or complex functional test for your application or library.

    pytest是一个非常成熟的全功能的Python测试框架,主要有以下几个特点:

    • 简单灵活,容易上手
    • 支持参数化
    • 能够支持简单的单元测试和复杂的功能测试,还可以用来做selenium/appnium等自动化测试、接口自动化测试(pytest+requests)
    • pytest具有很多第三方插件,并且可以自定义扩展,比较好用的如pytest-selenium(集成selenium)、pytest-html(完美html测试报告生成)、pytest-rerunfailures(失败case重复执行)、pytest-xdist(多CPU分发)等
    • 测试用例的skip和xfail处理
    • 可以很好的和jenkins集成
    • report框架----allure 也支持了pytest

    1、pytest安装

    1.1安装

    pip install -U pytest

    1.2验证安装

    pytest --version # 会展示当前已安装版本

    1.3pytest文档

    官方文档:https://docs.pytest.org/en/latest/contents.html

    在pytest框架中,有如下约束:

    所有的单测文件名都需要满足test_*.py格式或*_test.py格式。
    在单测文件中,测试类以Test开头,并且不能带有 init 方法(注意:定义class时,需要以T开头,不然pytest是不会去运行该class的)
    在单测类中,可以包含一个或多个test_开头的函数。
    此时,在执行pytest命令时,会自动从当前目录及子目录中寻找符合上述约束的测试函数来执行。

    1.4 Pytest运行方式

     # file_name: test_abc.py
     import pytest # 引入pytest包
     def test_a(): # test开头的测试函数
         print("------->test_a")
         assert 1 # 断言成功
     def test_b():
         print("------->test_b")
         assert 0 # 断言失败
     if __name__ == '__main__':
            pytest.main("-s  test_abc.py") # 调用pytest的main函数执行测试
    

    1.测试类主函数模式

      pytest.main("-s  test_abc.py")
    

    2.命令行模式

      pytest 文件路径/测试文件名
      例如:pytest ./test_abc.py
    

    1.5 Pytest Exit Code 含义清单

    • Exit code 0 所有用例执行完毕,全部通过
    • Exit code 1 所有用例执行完毕,存在Failed的测试用例
    • Exit code 2 用户中断了测试的执行
    • Exit code 3 测试执行过程发生了内部错误
    • Exit code 4 pytest 命令行使用错误
    • Exit code 5 未采集到可用测试用例文件

    1.6 如何获取帮助信息

    查看 pytest 版本

    pytest --version
    

    显示可用的内置函数参数

    pytest --fixtures
    

    通过命令行查看帮助信息及配置文件选项

    pytest --help
    

    1.7 控制测试用例执行

    1.在第N个用例失败后,结束测试执行

    pytest -x                    # 第01次失败,就停止测试
    pytest --maxfail=2     # 出现2个失败就终止测试
    

    2.指定测试模块

    pytest test_mod.py
    

    3.指定测试目录

    pytest testing/
    

    4.通过关键字表达式过滤执行

    pytest -k "MyClass and not method"
    

    这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple

    5.通过 node id 指定测试用例

    nodeid由模块文件名、分隔符、类名、方法名、参数构成,举例如下:
    运行模块中的指定用例

    pytest test_mod.py::test_func
    

    运行模块中的指定方法

    ytest test_mod.py::TestClass::test_method
    

    6.通过标记表达式执行

    pytest -m slow
    

    这条命令会执行被装饰器 @pytest.mark.slow 装饰的所有测试用例

    7.通过包执行测试

    pytest --pyargs pkg.testing
    

    这条命令会自动导入包 pkg.testing,并使用该包所在的目录,执行下面的用例。

    1.8 多进程运行cases

    当cases量很多时,运行时间也会变的很长,如果想缩短脚本运行的时长,就可以用多进程来运行。

    安装pytest-xdist:

    pip install -U pytest-xdist
    

    运行模式:

    pytest test_se.py -n NUM
    

    其中NUM填写并发的进程数。

    1.9 重试运行cases

    在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。

    安装pytest-rerunfailures:

    pip install -U pytest-rerunfailures
    

    运行模式:

    pytest test_se.py --reruns NUM
    

    NUM填写重试的次数。

    1.10 显示print内容

    在运行测试脚本时,为了调试或打印一些内容,我们会在代码中加一些print内容,但是在运行pytest时,这些内容不会显示出来。如果带上-s,就可以显示了。

    运行模式:

    pytest test_se.py -s
    

    另外,pytest的多种运行模式是可以叠加执行的,比如说,你想同时运行4个进程,又想打印出print的内容。可以用:

    pytest test_se.py -s -n 4
    

     

    2.Pytest的setup和teardown函数

    1.setup和teardown主要分为:模块级,类级,功能级,函数级。
    2.存在于测试类内部
    代码示例:

    • 函数级别setup()/teardown()

    运行于测试方法的始末,即:运行一次测试函数会运行一次setup和teardown

    import pytest
    class Test_ABC:
      # 函数级开始
      def setup(self):
          print("------->setup_method")
      # 函数级结束
      def teardown(self):
          print("------->teardown_method")
      def test_a(self):
          print("------->test_a")
          assert 1
      def test_b(self):
          print("------->test_b")
    if __name__ == '__main__':
                  pytest.main("-s  test_abc.py")
    
    执行结果:
      test_abc.py 
      ------->setup_method # 第一次 setup()
      ------->test_a
      .
      ------->teardown_method # 第一次 teardown()
      ------->setup_method # 第二次 setup()
      ------->test_b
      .
              ------->teardown_method # 第二次 teardown()
    
    • 2.2.类级别

    运行于测试类的始末,即:在一个测试内只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。
    代码示例:

    import pytest
    class Test_ABC:
       # 测试类级开始
       def setup_class(self):
           print("------->setup_class")
       # 测试类级结束
       def teardown_class(self):
           print("------->teardown_class")
       def test_a(self):
           print("------->test_a")
           assert 1
       def test_b(self):
           print("------->test_b")
              if __name__ == '__main__':
                  pytest.main("-s  test_abc.py")
    
    执行结果:
      test_abc.py 
      ------->setup_class # 第一次 setup_class()
      ------->test_a
      .
      ------->test_b
      F 
              ------->teardown_class # 第一次 teardown_class()
    

    3.Pytest配置文件

    pytest的配置文件通常放在测试目录下,名称为pytest.ini,命令行运行时会使用该配置文件中的配置.

    #配置pytest命令行运行参数
       [pytest]
        addopts = -s ... # 空格分隔,可添加多个命令行参数 -所有参数均为插件包的参数配置测试搜索的路径
        testpaths = ./scripts  # 当前目录下的scripts文件夹 -可自定义
    #配置测试搜索的文件名称
        python_files = test*.py 
    #当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件 -可自定义
    配置测试搜索的测试类名
        python_classes = Test_*  
    
       #当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类 -可自定义
    配置测试搜索的测试函数名
      
        python_functions = test_*
    
    #当前目录下的scripts文件夹下,以test开头,以.py结尾的所有文件中,以Test开头的类内,以test_开头的方法 -可自定义
    
    

    4 Pytest常用插件

    插件列表网址:https://plugincompat.herokuapp.com
    包含很多插件包,大家可依据工作的需求选择使用。

    4.1 前置条件:

    1.文件路径:

    - Test_App
    - - test_abc.py
    - - pytest.ini
    

    2.pyetst.ini配置文件内容:

      [pytest]
    # 命令行参数
     addopts = -s
    # 搜索文件名
     python_files = test_*.py
     # 搜索的类名
     python_classes = Test_*
     #搜索的函数名
        python_functions = test_*
    

    4.2 Pytest测试报告

    pytest-HTML是一个插件,pytest用于生成测试结果的HTML报告。兼容Python 2.7,3.6

    安装方式:pip install pytest-html

    pip install pytest-html

    通过命令行方式,生成xml/html格式的测试报告,存储于用户指定路径。插件名称:pytest-html

    使用方法: 命令行格式:pytest --html=用户路径/report.html

    示例:

    import pytest
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
            print("------->teardown_class")
        def test_a(self):
            print("------->test_a")
            assert 1
        def test_b(self):
                print("------->test_b")
                assert 0 # 断言失败```
    运行方式:
    1.修改Test_App/pytest.ini文件,添加报告参数,即:addopts = -s --html=./report.html 
        # -s:输出程序运行信息
        # --html=./report.html 在当前目录下生成report.html文件
        ️ 若要生成xml文件,可将--html=./report.html 改成 --html=./report.xml
    2.命令行进入Test_App目录
    3.执行命令: pytest
    执行结果:
        1.在当前目录会生成assets文件夹和report.html文件
    
    

    5.pytest的高阶用法(一)

    前置条件:

    1.文件路径:

    Test_App
        - - test_abc.py
        - - pytest.ini
    

    2.pyetst.ini配置文件内容:

    
    [pytest]
      命令行参数
     addopts = -s
     搜索文件名
     python_files = test*.py
      搜索的类名
     python_classes = Test*
    搜索的函数名
     python_functions = test_*
    

    5.1pytest之fixture

    fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。

    方法:fixture(scope="function", params=None, autouse=False, ids=None, name=None)
    常用参数:

     scope:被标记方法的作用域
     function" (default):作用于每个测试方法,每个test都运行一次
    "class":作用于整个类,每个class的所有test只运行一次
     "module":作用于整个模块,每个module的所有test只运行一次
     "session:作用于整个session(慎用),每个session只运行一次
     params:(list类型)提供参数数据,供调用标记方法的函数使用
     autouse:是否自动运行,默认为False不运行,设置为True自动运行
    

    5.2fixture第一个例子(通过参数引用)

    示例:

    class Test_ABC:
        @pytest.fixture()
        def before(self):
            print("------->before")
        def test_a(self,before): # ️ test_a方法传入了被fixture标识的函数,已变量的形式
            print("------->test_a")
            assert 1
    if __name__ == '__main__':
        pytest.main("-s  test_abc.py")
    执行结果:
        test_abc.py 
            ------->before # 发现before会优先于测试函数运行
            ------->test_a
             .
    

    5.3.fixture第二个例子(通过函数引用)

    示例:

    import pytest
    @pytest.fixture() # fixture标记的函数可以应用于测试类外部
    def before():
        print("------->before")
    @pytest.mark.usefixtures("before")
    class Test_ABC:
        def setup(self):
            print("------->setup")
        def test_a(self):
            print("------->test_a")
            assert 1
    if __name__ == '__main__':
              pytest.main("-s  test_abc.py")
      执行结果:
          test_abc.py 
          ------->before # 发现before会优先于测试类运行
          ------->setup
          ------->test_a
          .
    

    5.4.fixture第三个例子(默认设置为运行)

    示例:

     import pytest
     @pytest.fixture(autouse=True) # 设置为默认运行
     def before():
         print("------->before")
     class Test_ABC:
         def setup(self):
             print("------->setup")
         def test_a(self):
             print("------->test_a")
             assert 1
     if __name__ == '__main__':
         pytest.main("-s  test_abc.py")
    执行结果:
        test_abc.py 
        ------->before # 发现before自动优先于测试类运行
        ------->setup
        ------->test_a
            .
    

    5.5.fixture第四个例子(设置作用域为function)

    示例:

        import pytest
        @pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行
        def before():
            print("------->before")
        class Test_ABC:
            def setup(self):
                print("------->setup")
            def test_a(self):
                print("------->test_a")
                assert 1
            def test_b(self):
                print("------->test_b")
                assert 1
        if __name__ == '__main__':
            pytest.main("-s  test_abc.py")
    执行结果:
        test_abc.py
            ------->before # 运行第一次
            ------->setup
            ------->test_a
            .------->before # 运行第二次
            ------->setup
            ------->test_b
            .
    

    5.6.fixture第五个例子(设置作用域为class)

    示例:

        import pytest
        @pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行
        def before():
            print("------->before")
        class Test_ABC:
            def setup(self):
                print("------->setup")
            def test_a(self):
                print("------->test_a")
                assert 1
            def test_b(self):
                print("------->test_b")
                assert 1
        if __name__ == '__main__':
            pytest.main("-s  test_abc.py")
    执行结果:
        test_abc.py
        ------->before # 发现只运行一次
        ------->setup
            ------->test_a
            .
            ------->setup
            ------->test_b
            .
    

    5.7.fixture第六个例子(返回值)

    示例一:

    
        import pytest
        @pytest.fixture()
        def need_data():
            return 2 # 返回数字2
    
        class Test_ABC:
    
            def test_a(self,need_data):
                print("------->test_a")
                assert need_data != 3 # 拿到返回值做一次断言
    
        if __name__ == '__main__':
            pytest.main("-s  test_abc.py")
    执行结果:
        test_abc.py 
        ------->test_a
        .
    ``
    
    

    示例二:

    import pytest
    @pytest.fixture(params=[1, 2, 3])
    def need_data(request): # 传入参数request 系统封装参数
        return request.param # 取列表中单个值,默认的取值方式
    class Test_ABC:
    
        def test_a(self,need_data):
            print("------->test_a")
            assert need_data != 3 # 断言need_data不等于3
    
    if __name__ == '__main__':
        pytest.main("-s  test_abc.py")
    
     执行结果:
          # 可以发现结果运行了三次
          test_abc.py 
          1
          ------->test_a
          .
          2
          ------->test_a
          .
          3
          ------->test_a
          F
    

    6.Pytest高阶用法(二)

    前置条件:

    1.文件路径:

    - Test_App
    - - test_abc.py
    - - pytest.ini
    

    2.pyetst.ini配置文件内容:

    [pytest]
    命令行参数
    addopts = -s
    搜索文件名
    python_files = test_*.py
     搜索的类名
    python_classes = Test_*
     搜索的函数名
    python_functions = test_*
    

    6.1.跳过测试函数

    根据特定的条件,不执行标识的测试函数.
     方法:
         skipif(condition, reason=None)
     参数:
         condition:跳过的条件,必传参数
         reason:标注原因,必传参数
     使用方法:
         @pytest.mark.skipif(condition, reason="xxx") 
    

    示例:

    import pytest
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
            print("------->teardown_class")
        def test_a(self):
            print("------->test_a")
            assert 1
        @pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b
        def test_b(self):
            print("------->test_b")
                assert 0
    执行结果:
       test_abc.py 
       ------->setup_class
       ------->test_a #只执行了函数test_a
       .
       ------->teardown_class
           s # 跳过函数```
    
    
    

    6.2 标记为预期失败函数

    标记测试函数为失败函数
     方法:
         xfail(condition=None, reason=None, raises=None, run=True, strict=False)
     常用参数:
         condition:预期失败的条件,必传参数
         reason:失败的原因,必传参数
     使用方法:
         @pytest.mark.xfail(condition, reason="xx")
    

    示例:

    import pytest
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
            print("------->teardown_class")
        def test_a(self):
            print("------->test_a")
            assert 1
        @pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b
           def test_b(self):
               print("------->test_b")
              assert 0
       执行结果:
           test_abc.py 
           ------->setup_class
           ------->test_a
           .
           ------->test_b
           ------->teardown_class
           x  # 失败标记
    

    6.3 函数数据参数化

    方便测试函数对测试属于的获取。
     方法:
         parametrize(argnames, argvalues, indirect=False, ids=None, scope=None)
     常用参数:
         argnames:参数名
         argvalues:参数对应值,类型必须为list
                     当参数为一个时格式:[value]
                     当参数个数大于一个时,格式为:[(param_value1,param_value2.....),(param_value1,param_value2.....)]
     使用方法:
         @pytest.mark.parametrize(argnames,argvalues)
         ️ 参数值为N个,测试方法就会运行N次
      
    
    

    单个参数示例:

    import pytest
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
            print("------->teardown_class")
    
    @pytest.mark.parametrize("a",[3,6]) # a参数被赋予两个值,函数会运行两遍
    def test_a(self,a): # 参数必须和parametrize里面的参数一致
        print("test data:a=%d"%a)
        assert a%3 == 0
        执行结果:
        test_abc.py 
        ------->setup_class
        test data:a=3 # 运行第一次取值a=3
        .
        test data:a=6 # 运行第二次取值a=6
        . 
        ------->teardown_class
    

    多个参数示例:

    import pytest
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
            print("------->teardown_class")
    
    @pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 参数a,b均被赋予两个值,函数会运行两遍
    def test_a(self,a,b): # 参数必须和parametrize里面的参数一致
        print("test data:a=%d,b=%d"%(a,b))
        assert a+b == 3
        执行结果:
        test_abc.py 
        ------->setup_class
        test data:a=1,b=2 # 运行第一次取值 a=1,b=2
        .
        test data:a=0,b=3 # 运行第二次取值 a=0,b=3
        .
        ------->teardown_class
    

    函数返回值类型示例:

    import pytest
    def return_test_data():
        return [(1,2),(0,3)]
    class Test_ABC:
        def setup_class(self):
            print("------->setup_class")
        def teardown_class(self):
                print("------->teardown_class")
    
    @pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值
    def test_a(self,a,b):
        print("test data:a=%d,b=%d"%(a,b))
        assert a+b == 3
        
        执行结果:
        test_abc.py 
        ------->setup_class
        test data:a=1,b=2 # 运行第一次取值 a=1,b=2
        .
        test data:a=0,b=3 # 运行第二次取值 a=0,b=3
        .
            ------->teardown_class
    

    6.4 修改 Python traceback 输出

    pytest --showlocals     # show local variables in tracebacks
    pytest -l               # show local variables (shortcut)
    pytest --tb=auto        # (default) 'long' tracebacks for the first and last
                            # entry, but 'short' style for the other entries
    pytest --tb=long        # exhaustive, informative traceback formatting
    pytest --tb=short       # shorter traceback format
    pytest --tb=line        # only one line per failure
    pytest --tb=native      # Python standard library formatting
    pytest --tb=no          # no traceback at all
    

    --full-trace 参数会打印更多的错误输出信息,比参数 --tb=long 还多,即使是 Ctrl+C 触发的错误,也会打印出来

    6.5 执行失败的时候跳转到 PDB

    执行用例的时候,跟参数 --pdb,这样失败的时候,每次遇到失败,会自动跳转到 PDB

    pytest --pdb              # 每次遇到失败都跳转到 PDB
    pytest -x --pdb           # 第一次遇到失败就跳转到 PDB,结束测试执行
    pytest --pdb --maxfail=3  # 只有前三次失败跳转到 PDB 
    

    6.6 设置断点

    在用例脚本中加入如下python代码,pytest会自动关闭执行输出的抓取,这里,其他test脚本不会受到影响,带断点的test上一个test正常输出

     import pdb; pdb.set_trace()
    

    6.7 获取用例执行性能数据

    获取最慢的10个用例的执行耗时

    pytest --durations=10
    

    6.8 生成 JUnitXML 格式的结果文件

    这种格式的结果文件可以被Jenkins或其他CI工具解析

    pytest --junitxml=path
    

    6.9禁用插件 

    例如,关闭 doctest 插件

    pytest -p no:doctest
    

    6.10 从Python代码中调用pytest

    pytest.main()                      # 基本用法
    pytest.main(['-x', 'mytestdir'])   # 传入配置参数
    
    
    // 指定自定义的或额外的插件
    # content of myinvoke.py
    import pytest
    class MyPlugin(object):
        def pytest_sessionfinish(self):
            print("*** test run reporting finishing")
    
    pytest.main(["-qq"], plugins=[MyPlugin()])
    

    6.11 测试脚本迁移后快速部署包含pytest的virtualenv

    例如你从Gitlab仓库里clone了项目组的刀刀同学编写的测试脚本到你自己的电脑里,你想修改些东西,并调试,咋办?可以通过下面的操作快速创建 VirtualEnv

    cd <repository>
    pip install -e .
    

    This will set up a symlink to your code in site-packages, allowing you to edit your code while
    your tests run against it as if it were installed.
    Setting up your project in development mode lets you avoid having to reinstall every time you want to run your tests,
    and is less brittle than mucking about with sys.path to point your tests at local code.
    Also consider using tox

     

    遇到的问题:

    问题:
    pytest可以输出覆盖率的html报告

    使用命令如下:

    pytest -vv --cov=./ --cov-report=html
    open htmlcov/index.html 

    有可能遇到报错:

    (venv) zhangxiaofans-MacBook-Pro:mgap-mendel joe$ pytest --cov-report=html
    usage: pytest [options] [file_or_dir] [file_or_dir] [...]
    pytest: error: unrecognized arguments: --cov-report=html
      inifile: None
      rootdir: /Users/joe/workspace/platform/mgap-mendel/mgap-mendel

    原因:
    缺少pytest cov的包

    解决方法
     

    pip install pytest-cov

     

     

     

     

     

    展开全文
  • IDEA中使用JUnit4(单元测试框架)超详细!

    万次阅读 多人点赞 2019-08-20 17:25:23
    IDEA中使用JUnit4教程 超详细!(单元测试框架) 自动化测试的必经之路--Selenium

    IDEA中使用JUnit4教程 超详细!(单元测试框架)

    导语:自动化测试的必经之路–Selenium

    作者变优秀的小白

    GithubYX-XiaoBai

    QQ交流群(new): 811792998

    爱好Americano More Ice !

    话不多说,实战为主!

    注:如中途遇到不懂的地方,直接评论留言看到会马上答疑!

    首先我们要认识什么是JUnit

    JUnit单元测试框架由Erich Gamma和Kent Beck编写的一个回归测试框架(Regresion Testing Framework),主要用于Java语言程序的单元测试,目前使用的主流版本是JUnit以上版本。
    此测试框架可用于执行WebDriver的自动化测试框架,话不多说,实战为主。

    1.安装IDEA,配置好IDEA的环境(关于IDEA的配置就不在此说明了,度娘多详细的教程都有)
    2.打开IDEA,先新建一个Java项目,点击 文件File-新建New-项目Project

    3.然后下一步,下一步,我这里项目名使用 Junit4Proj,点击完成
    在这里插入图片描述
    4.完成项目的创建后,点击 文件File-设置Settings-Plugins 在搜索栏搜索 JUnit,此时出现了几个Plugins,选择 JUnit,有两种方法安装插件(我都框起来了)。Install JetBrains plugin…Browser repositories两种方法,前者直接点击下载就好,如图
    在这里插入图片描述
    也可用后者方法,相对麻烦了一点(第三种方法更麻烦就不列举了),如图
    在这里插入图片描述
    在这里插入图片描述
    5.当你下载好Junit4 插件后,打开 文件File-设置Settings,如图注明修改配置
    在这里插入图片描述
    JUnit4 模块里找到此代码 将test去掉
    在这里插入图片描述
    6.以下是我编写的被测试类Calculator,以它为例。操作步骤为:IDEA新建一个java工程–自定义命名新建package–此package下新建Calculator类
    在这里插入图片描述
    代码如下

    public class Calculator {
        public int result=0;
        public int add(int operand1,int operand2){
            result=operand1+operand2;   //将两个传入参数进行相加操作
            return result;
        }
        public  int subtract(int operand1,int operand2){
            result=operand1-operand2;   //将两个传入参数进行相减操作
            return  result;
        }
        public int multipe(int operand1,int operand2){
            result=operand1*operand2;   //将两个传入参数进行相乘操作
            for(;;){                    //死循环
            }
        }
        public int divide(int operand1,int operand2){
            result=operand1/0;      //除0操作
            return result;
        }
        public int getResult(){
            return this.result;     //返回计算结果
        }
    
    }
    
    

    7.创建Junit4的测试代码,有两种方法(超级简单方便),第一种直接点击被测试类Calculator 使用 Ctrl+Shift+T
    在这里插入图片描述
    第二种方法 鼠标右键点击类名 使用 goto-Test(本人使用IDEA下错成汉化版表示难受)即可实现
    在这里插入图片描述
    8.创建测试,根据自身需要来勾选
    在这里插入图片描述
    我把测试类代码直接给出来,可根据需要自行修改

    import org.junit.*;
    
    import static org.junit.Assert.*;
    
    public class CalculatorTest {
    
        private static Calculator cal=new Calculator();
    
        @BeforeClass
        public static void setUpBeforeClass() throws Exception{
            System.out.println("@BeforeClass");
        }
        @AfterClass
        public static void tearDownAfterClass() throws Exception{
            System.out.println("@AfterClass");
        }
    
        @Before
        public void setUp() throws Exception {
            System.out.println("测试开始");
        }
    
        @After
        public void tearDown() throws Exception {
            System.out.println("测试结束");
        }
    
        @Test
        public void testAdd() {
            cal.add(2,2);
            assertEquals(4,cal.getResult());
            //fail("Not yet implemented");
        }
    
        @Test
        public void testSubtract() {
            cal.subtract(4,2);
            assertEquals(2,cal.getResult());
            //fail("Not yet implemented");
        }
    
        @Ignore
        public void testMultiply() {
            fail("Not yet implemented");
        }
    
        @Test(timeout = 2000)
        public void testDivide() {
            for(;;);
        }
    
        @Test(expected = ArithmeticException.class)
        public void testDivideByZero(){
            cal.divide(4,0);
        }
    
    
    }
    

    9.创建完成,有可能会报错,如图所描述的可能会报错
    在这里插入图片描述
    解决方法:找到项目的 pom.xml文件,使用maven直接导入项目
    在这里插入图片描述
    以下是本测试类需要用到的dependency,可自行复制

        <dependencies>
            <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
            <dependency>
                <groupId>org.seleniumhq.selenium</groupId>
                <artifactId>selenium-java</artifactId>
                <version>3.14.0</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.jboss.shrinkwrap</groupId>
                <artifactId>shrinkwrap-api</artifactId>
                <version>1.2.6</version>
            </dependency>
            <dependency>
                <groupId>org.jboss.arquillian.junit</groupId>
                <artifactId>arquillian-junit-container</artifactId>
                <version>1.4.1.Final</version>
                <scope>test</scope>
            </dependency>
    
        </dependencies>
    

    10.所有工作已经做完,此时点击运行就可以啦!运行结果如下:
    在这里插入图片描述

    此时一个简单的Junit注解使用就算成功了!

    最后附上一些使用到的概念:
    一个测试类中只能声明此注解一次,此注解对应的方法只能被执行一次
    @BeforeClass 使用此注解的方法在测试类被调用之前执行
    @AfterClass 使用此注解的方法在测试类被调用结束退出之前执行
    一个类中有多少个@Test注解方法,以下对应注解方法就被调用多少次
    @Before 在每个@Test调用之前执行
    @After 在每个@Test调用之后执行
    @Test 使用此注解的方法为一个单元测试用例,一个测试类中可多次声明,每个注解为@Test只执行一次
    @Ignore 暂不执行的测试用例,会被JUnit4忽略执行

    总结: 大家如果有什么疑问或者建议的地方,可直接留言评论!本人会一一回复!!

    展开全文
  • Robot Framework 自动化测试框架

    万人学习 2019-11-29 14:57:20
    Robot Framework 自动化测试框架,包括接口测试、数据库测试、Web测试、App测试。
  • 【Pytest】python单元测试框架pytest简介

    万次阅读 多人点赞 2015-06-15 11:41:16
    pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍,它具有如下特点: 非常容易上手,入门简单,文档丰富,文档中...

    最新更新请看 《基于Pytest框架的自动化测试开发实践(万字长文入门篇)》

    1、Pytest介绍

    pytest是python的一种单元测试框架,与python自带的unittest测试框架类似,但是比unittest框架使用起来更简洁,效率更高。根据pytest的官方网站介绍,它具有如下特点:

     

    • 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考
    • 能够支持简单的单元测试和复杂的功能测试
    • 支持参数化
    • 执行测试过程中可以将某些测试跳过,或者对某些预期失败的case标记成失败
    • 支持重复执行失败的case
    • 支持运行由nose, unittest编写的测试case
    • 具有很多第三方插件,并且可以自定义扩展
    • 方便的和持续集成工具集成
    由于网上pytest的中文文档比较少,自己学习过程中,基本上看的就是英文的官方文档,对于不想看英文的同学们,本系列文章希望能够帮大家一马。
     

    2、安装pytest

    与安装其他的python软件无异,直接使用pip安装。
    pip install -U pytest
    安装完成后,可以验证安装的版本:
    py.test --version
     

    3、一个实例

    我们可以通过下面的实例,看看使用py.test进行测试是多么简单。
    # content of test_sample.py
    
    def func(x):
        return x+1
    
    def test_func():
        assert func(3) == 5
    这里我们定义了一个被测试函数func,该函数将传递进来的参数加1后返回。我们还定义了一个测试函数test_func用来对func进行测试。test_func中我们使用基本的断言语句assert来对结果进行验证。
    下面来运行这个测试:
    $ py.test
    =========================== test session starts ============================
    platform linux -- Python 3.4.1 -- py-1.4.27 -- pytest-2.7.1
    rootdir: /tmp/doc-exec-101, inifile:
    collected 1 items
    test_sample.py F
    ================================= FAILURES =================================
    _______________________________ test_answer ________________________________
    def test_answer():
    > assert func(3) == 5
    E assert 4 == 5
    E + where 4 = func(3)
    test_sample.py:5: AssertionError
    ========================= 1 failed in 0.01 seconds =========================
    执行测试的时候,我们只需要在测试文件test_sample所在的目录下,运行py.test即可。pytest会在当前的目录下,寻找以test开头的文件(即测试文件),找到测试文件之后,进入到测试文件中寻找test_开头的测试函数并执行。
    通过上面的测试输出,我们可以看到该测试过程中,一个收集到了一个测试函数,测试结果是失败的(标记为F),并且在FAILURES部分输出了详细的错误信息,帮助我们分析测试原因,我们可以看到"assert func(3) == 5"这条语句出错了,错误的原因是func(3)=4,然后我们断言func(3) 等于 5。
     

    4、再一个实例

    当需要编写多个测试样例的时候,我们可以将其放到一个测试类当中,如:
    # content of test_class.py
    
    class TestClass:
        def test_one(self):
            x = "this"
            assert 'h' in x
    
        def test_two(self):
            x = "hello"
            assert hasattr(x, 'check')
    
    我们可以通过执行测试文件的方法,执行上面的测试:
    $ py.test -q test_class.py
    .F
    ================================= FAILURES =================================
    ____________________________ TestClass.test_two ____________________________
    self = <test_class.TestClass object at 0x7fbf54cf5668>
    def test_two(self):
    x = "hello"
    > assert hasattr(x, 'check')
    E assert hasattr('hello', 'check')
    test_class.py:8: AssertionError
    1 failed, 1 passed in 0.01 seconds
    
    从测试结果中可以看到,该测试共执行了两个测试样例,一个失败一个成功。同样,我们也看到失败样例的详细信息,和执行过程中的中间结果。
     

    5、如何编写pytest测试样例

    通过上面2个实例,我们发现编写pytest测试样例非常简单,只需要按照下面的规则:
    • 测试文件以test_开头(以_test结尾也可以)
    • 测试类以Test开头,并且不能带有 __init__ 方法
    • 测试函数以test_开头
    • 断言使用基本的assert即可

    6、如何执行pytest测试样例

    执行测试样例的方法很多种,上面第一个实例是直接执行py.test,第二个实例是传递了测试文件给py.test。其实py.test有好多种方法执行测试:
    py.test               # run all tests below current dir
    py.test test_mod.py   # run tests in module
    py.test somepath      # run all tests below somepath
    py.test -k stringexpr # only run tests with names that match the
                          # the "string expression", e.g. "MyClass and not method"
                          # will select TestMyClass.test_something
                          # but not TestMyClass.test_method_simple
    py.test test_mod.py::test_func # only run tests that match the "node ID",
    			       # e.g "test_mod.py::test_func" will select
                                   # only test_func in test_mod.py

    7、测试报告

    pytest可以方便的生成测试报告,即可以生成HTML的测试报告,也可以生成XML格式的测试报告用来与持续集成工具集成。

    生成HTML格式报告:

     

    py.test --resultlog=path
    生成XML格式的报告:

     

     

    py.test --junitxml=path

     

    8、如何获取帮助信息

    py.test --version # shows where pytest was imported from
    py.test --fixtures # show available builtin function arguments
    py.test -h | --help # show help on command line and config file options
    

    9、最佳实践

    其实对于测试而言,特别是在持续集成环境中,我们的所有测试最好是在虚拟环境中。这样不同的虚拟环境中的测试不会相互干扰的。
    由于我们的实际工作中,在同一个Jekins中,运行了好多种不同项目册的测试,因此,各个测试项目运行在各自的虚拟环境中。
    将pytest安装在虚拟环境中:
    1、将当前目录创建为虚拟环境
    virtualenv .        # create a virtualenv directory in the current directory
    source bin/activate # on unix
    
    2、在虚拟环境中安装pytest:
    pip install pytest


     
     

     

    展开全文
  • 2019 Python接口自动化测试框架实战开发(一)

    万次阅读 多人点赞 2019-06-28 15:55:25
    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!...整个项目分为四个部分:接口基础丶接口开发丶Unittest与接口测试结合以及接口自动化框架从设计到开发 接口基础包括:H...
  • unittest测试框架

    千次阅读 2020-06-12 20:42:02
    unittest 是python 的单元测试框架,unittest 单元测试提供了创建测试用例,测试套件以及批量执行的方案, unittest 在安装pyhton 以后就直接自带了,直接import unittest 就可以使用。 作为单元测试的框架, ...
  • 在这个自动化测试框架中。 在config目录中存放的是测试配置相关的文件,配置文件可以使用ini、xml、yml等文件类型。例如,要测试的网址、调试日志的文件名、日志的输出格式等 在data目录中存放的是需要测试的数据。...
  • TestNG测试框架

    万次阅读 2018-08-07 16:01:29
    part 1、认识单元测试框架TestNG Java语言的单元测试框架 . JUnit4,要求JDK1.5及以上版本 . TestNG,同理 单元测试框架的作用 . 使测试代码和产品代码分离 . 简化测试代码的编写 . 灵活组织单元测试用例 . ...
  • Python必会的单元测试框架 —— unittest

    万次阅读 多人点赞 2016-10-27 12:52:37
    用Python搭建自动化测试框架,我们需要组织用例以及测试执行,这里博主推荐Python的标准库——unittest。 unittest是xUnit系列框架中的一员,如果你了解xUnit的其他成员,那你用unittest来应该是很轻松的,它们的...
  • 自动化测试框架[Cypress测试用例]

    万次阅读 2021-01-03 21:46:40
    Cypress底层依赖于很多优秀的开源测试库,其中比较重要的就是Mocha,它是一个适用于Node.js和浏览器的测试框架,它使得异步测试变得简单灵活;而JavaScript是单线程异步执行的,这就产生了一种复杂的场景,因为异步...
  • 基于Python的HTTP接口自动化测试框架实现

    万次阅读 多人点赞 2011-04-22 14:36:00
    基于Python的HTTP接口自动化测试框架实现 作者:张元礼 http://blog.csdn.net/vincetest 一、测试需求描述 对服务后台一系列的http接口功能测试。 输入:根据接口描述构造不同的参数输入值 输出:XML文件 eg:...
  • Java测试框架

    千次阅读 2017-08-31 15:01:01
    前言在项目开发过程中必不可少的会用到测试框架来检查自己的代码逻辑,可能大多数人和我一样从来没有怎么重视过测试代码,认为测试代码存在与否的意义不大。但是,看过很多大牛的项目后,发现他们写的项目中测试用例...
  • 自动化测试框架

    千次阅读 2019-05-27 09:59:24
    自动化测试框架=编程语言+单元测试框架+持续集成工具+数据库+项目管理工具+版本管理工具 编程语言:java,python 单元测试框架: testNG/Junit是java中的单元测试框架 unittest/pytest是python中的单元测试框架 ...
  • 软件测试框架

    千次阅读 2019-02-25 16:38:48
    selenium:web自动化测试框架 据 Selenium 主页所说,与其他测试工具相比,使用 Selenium 的最大好处是: Selenium 测试直接在浏览器中运行,就像真实用户所做的一样。Selenium 测试可以在 Windows、Linux 和 ...
  • Truffle测试框架

    千次阅读 2018-06-08 19:01:19
    Truffle测试框架Truffle 有一个标准的自动化测试框架,让你可以非常方便地测试您的合约.这个框架允许您以两种不同的方式编写简单可控的测试: 1. 在JavaScript中, 用于执行来自外部世界的合约,就像您的应用程序一样...
  • python:Unittest单元测试框架

    万次阅读 2019-06-12 20:39:05
    Unittest单元测试框架 unittest是什么 unittest是python内置的单元测试框架,具备编写用例、组织用例、执行用例、输出报告等自动化框架的条件。 单元测试框架的优点 一般来说不用单元测试框架也能编写单元测试...
  • 【自动化测试】自动化测试框架与工具

    千次阅读 热门讨论 2021-05-06 21:01:01
    文章目录1)什么是自动化测试框架?...1.4 判断是否需要自动化测试适用项目:2)自动化测试框架的类型2.1 自动化测试框架的分类2.1.1 基于模块的测试框架:2.1.2 库架构测试框架2.1.3 数据驱动测试框架2.1.4 关键字驱动
  • 【功能测试】功能测试-测试框架

    千次阅读 2018-06-28 16:23:28
    在公司做了一次功能测试的测试框架分享,记录下来
  • Spring测试框架

    千次阅读 2018-08-20 17:51:26
    传统测试存在的问题 1.每个测试都要重新启动Spring,启动容器的开销大,测试效率低下 ...如何使用Spring的测试框架: &lt;dependency&gt; &lt;groupId&gt;junit&lt;/groupI...
  • 大神总结超详细unittest单元测试框架总结

    万次阅读 多人点赞 2018-10-20 20:36:43
    unittest单元测试框架总结  unittest单元测试框架不仅可以适用于单元测试,还可以适用WEB自动化测试用例的开发与执行,该测试框架可组织执行测试用例,并且提供了丰富的断言方法,判断测试用例是否通过,最终生成...
  • GTest测试框架使用

    千次阅读 2020-04-26 15:08:38
    Gtest是Google Test的简称,是Google开发的C++单元测试框架; 适用于多个平台: Liunx, Mac OS X, Windows, Cygwin, Windows CE and Symbian, PlatformIO Gtest 是基于xUnit框架编写,和Junit, PyUnit 非常相似 ...
  • 本篇开始将介绍几个重量级的测试框架,首先介绍的是Android最早推出的便于进行程序深入的,系统性的单元测试的框架–Instrumentation。Instrumentation从android2.3甚至更早版本就存在了,很多Android自动化测试框架...
  • 测试框架Mocha

    千次阅读 2017-06-19 16:56:43
    Mocha 是一种前端测试框架,由于它非常适合做 TDD(测试驱动开发),深受欢迎。官网:https://mochajs.org/ 所谓测试驱动开发(英语:Test-driven development,缩写为 TDD)是一种软件开发过程中的应用方法,由极限...
  • 【附源码】测试框架总体来说与应用开发的框架并无太大的差异,在设计的起始阶段初衷也都一样,满足测试绝大多数活动并提高测试代码的编写效率,然后兼顾易用、兼容、通用以及简单维护等几个维度是其存在的唯一意义,...
  • RobotFramwork测试框架做接口测试

    千次阅读 2016-06-23 15:59:36
    前段时间为了解决单位的接口测试问题,便在robotframwork框架上研究了下接口测试的一些技术,便发现robotframwork测试框架可以很好的支持接口测试,当然这期间也是需要朋友们根据自己项目或业务的需求去使用python...
  • Test Runner(测试运行器)是测试框架的重要组成部分,他用于组装待运行的测试用例及其配置,然后按照指定的要求运行这些测试用例,并将测试结果写入控制台或日志文件
  • 在做Android自动化测试框架开发之前,我们应该先了解目前的发展状况,知道我们的目标是什么,也要了解当前的主流框架,做到知己知彼,取长补短。 首先要思考和了解几个问题: 我们开发自动化框架的目标是什么?如何...
  • 测试框架-testng

    千次阅读 2019-02-16 16:13:27
    目录 ...TestNG是一个测试框架,其灵感来自JUnit和NUnit的,但引入了一些新的功能,使其功能更强大,使用更方便。 TestNG是一个开源自动化测试框架;TestNG表示下一代。 TestNG是类似于JUnit(特别...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 98,091
精华内容 39,236
关键字:

测试框架