自动化测试框架 订阅
自动化测试框架,即是应用于自动化测试所用的框架。按照框架的定义,自动化测试框架要么是提供可重用的基础自动化测试模块,如:selenium [1]  、watir等,它们主要提供最基础的自动化测试功能,比如打开一个程序,模拟鼠标和键盘来点击或操作被测试对象,最后验证被测对象的属性以判断程序的正确性;要么是可以提供自动化测试执行和管理功能的架构模块,如:Phoenix Framework,robot [2]  ,STAF [3]  等,它们本身不提供基础的自动化测试支持,只是用于组织、管理和执行那些独立的自动化测试用例,测试完成后统计测试结果,通常这类框架一般都会集成一个基础自动化测试模块,如:robot框架就可以集成selenium [4]  框架,Phoenix Framework集成的也是selenium框架。 展开全文
自动化测试框架,即是应用于自动化测试所用的框架。按照框架的定义,自动化测试框架要么是提供可重用的基础自动化测试模块,如:selenium [1]  、watir等,它们主要提供最基础的自动化测试功能,比如打开一个程序,模拟鼠标和键盘来点击或操作被测试对象,最后验证被测对象的属性以判断程序的正确性;要么是可以提供自动化测试执行和管理功能的架构模块,如:Phoenix Framework,robot [2]  ,STAF [3]  等,它们本身不提供基础的自动化测试支持,只是用于组织、管理和执行那些独立的自动化测试用例,测试完成后统计测试结果,通常这类框架一般都会集成一个基础自动化测试模块,如:robot框架就可以集成selenium [4]  框架,Phoenix Framework集成的也是selenium框架。
信息
定义来分类
基础功能测试框架、管理执行框架
部署方式来分
单机自动化测试框架
中文名
自动化测试框架
简    介
应用于自动化测试所用的框架
自动化测试框架涵义
什么是自动化测试框架在了解什么是自动化测试框架之前,先了解一下什么叫框架?框架是整个或部分系统的可重用设计,表现为一组抽象构件及构件实例间交互的方法;另一种定义认为,框架是可被应用开发者定制的应用骨架。前者是从应用方面,而后者是从目的方面给出的定义。 从框架的定义可以了解,框架可以是被重用的基础平台;框架也可以是组织架构类的东西。其实后者更为贴切,因为框和架本来就是组织和归类所用的。所以自动化测试框架的定义为:由一个或多个自动化测试基础模块、自动化测试管理模块、自动化测试统计模块等组成的工具集合。按框架的定义来分,自动化测试框架可以分为:基础功能测试框架、管理执行框架;按不同的测试类型来分,可以分为:功能自动化测试框架、性能自动化测试框架;按测试阶段来分,可以分为:单元自动化测试框架、接口自动化测试框架、系统自动化测试框架;按组成结构来分,可以分为:单一自动化测试框架、综合自动化测试框架;按部署方式来分,可以分为:单机自动化测试框架、分布式自动化测试框架。
收起全文
精华内容
下载资源
问答
  • 完整的框架源码下载...一、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试一下

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

    千次阅读 2018-05-09 15:00:15
    什么是自动化测试框架 自动化测试框架是应用于自动化测试的程序框架,它提供了可重用的自动化测试模块,提供最基础的自动化测试功能,或提供自动化测试执行和管理功能的架构模块。它是由一个或多个自动化测试基础...

    什么是自动化测试框架

            自动化测试框架是应用于自动化测试的程序框架,它提供了可重用的自动化测试模块,提供最基础的自动化测试功能,或提供自动化测试执行和管理功能的架构模块。它是由一个或多个自动化测试基础模块、自动化测试管理模块、自动化测试统计模块等组成的工具集合。

    自动化测试框架的作用

            (1)、能够有效组织和管理测试脚本  
            (2)、进行数据驱动或者关键字驱动的测试。
            (3)、将基础的测试代码进行封装,降低测试脚本编写的复杂性和重复性。
            (4)、提高测试将奥本维护和修改的效率。
            (5)、自动执行测试脚本,并自动发布测试报告,为持续集成的开发方式提供脚本支持。
            (6)、让不具备编程能力的测试工程师开展自动化测试工作。

    常见的几种测试框架

    数据驱动测试框架

    框架说明

           数据驱动测试是测试从数据文件(数据池,ODBC源,cvs文件,Excel文件,DAO对象等)中读取输入和输出数值并载入到捕获的或手工编码的脚本中变量里的一种框架。此框架可以将所有测试数据在自动化测试执行的过程中进行自动加载,动态判断测试结果是否符合预期结果,并自动输出测试报告。这个框架一般用于在一个测试流程中使用多组不同的测试数据,以此来验证被测试系统是否能正常工作。

            测试用例包含在数据文件里而不是在脚本里的方面上,这种框架和表格驱动测试有些相似;脚本只是一种“驱动器”(driver)或传送数据的机制。尽管导航的数据不包含在表结构中,但和表格驱动测试还是不同的。在数据驱动测试里,只有测试数据包含在数据文件中。

            这个框架趋向于减少你为了实现所有测试用例而需要的全部的脚本数量,并且在开发绕开错误的办法(Workaround)和维护方面提供了最好的灵活性。和表格驱动测试非常相似的是,表格驱动测试只需要非常少的代码就可以产生大量的测试用例。

    框架优点

            测试数据单独维护,变更测试数据不需要修改程序,不懂编程语言的测试人员也可以修改用例数据进行测试。

    框架缺点
            仅仅是将测试数据从测试脚本中独立出来,任何被测试程序的变更,都会导致测试程序需要进行修改,工作量是大部分架构里最大的,维护成本非常高。


    关键字驱动测试框架

    框架说明

           关键字驱动测试框架可以理解为高级的数据驱动测试框架。使用被操作的元素对象、操作的方法和操作的数据值作为测试过程中输入的自动化测试框架,简单表示为。item.operation(value)。被操作的元素对象、操作的方法和操作的数据值可以保存在数据数组、数据文件、数据库中作为关键字驱动测试框架的输入。

            关键字驱动测试框架属于更高级的自动化测试框架,可以兼容更多的自动化测试操作类型,大大提高了自动化测试框架的使用灵活性。

    框架优点

            极大的减少了自动化开发工程师维护量,普通测试工程师,可以很好的维护自身负责的模块中涉及的测试case和测试数据。

    框架缺点
            框架的抽象程度比较高,对自动化测试工程师的开发能力比较高


    模块化测试框架

    框架说明
            测试脚本模块化框架需要创建能够代表测试下应用程序(application-under-test)的模块,零件(Section)和函数的小的,独立的脚本。然后用一种分级的方式将这些小脚本组成更大的测试,实现一个特定的测试用例。这种框架最容易精通且掌握的。就在一个部件前面构建一个抽象层以掩藏应用程序其他的部件方面,它是一个很著名的编程策略。它把应用程序从在部件的修改中隔离开来并规定了在应用程序设计中的模块性。为了提高自动化测试套件(test suite)的可维护性和可测量性,测试脚本模块化框架应用了抽象或封装的原则。
    框架优点

            模块化框架是最容易掌握和使用的。在一个组件上方建立一个抽象层使其在余下的应用中隐藏起来,这是众所周知的编程技巧。这样应用同组件中的修改隔离开来,提供了程序设计的模块化特性。模块化测试脚本框架使用这一抽象或者封装的原理来提高自动测试组合的可维护性和可升级性。

    框架缺点

            几乎所有大的变更引起的工作量都由自动化测试开发工程师完成,控件识别和业务逻辑本身属于不同的领域,没有很好进行抽象封装。


    函数库结构框架

    框架说明

            测试库构架框架和测试脚本模块化框架非常相似,有着同样的优势,但是它把测试下的应用程序分成过程和函数,而不是脚本。这种框架要求创建代表测试下应用程序模块,零件和函数的库文件(SQABasic libraries, APIs, DLLs等等)。然后这些库文件被测试用例脚本直接调用。

    框架优点

            被测试系统无论是哪层发生变化,只需要相应的人员进行变更维护即可,完成了控件识别操作和业务逻辑的抽象分离。

    框架缺点

            变更引起的工作量还是附加在自动化测试开发工程师身上。

    混合测试自动化框架

    框架说明

            最常见的已实现的框架是上述技术的组合,抽取它们的优点,剔除其弱点。这种混合的测试自动化框架是发展时间较长且应用项目最多的框架。


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

    万人学习 2019-11-29 14:57:20
    Robot Framework 自动化测试框架,包括接口测试、数据库测试、Web测试、App测试。
  • 2019 Python接口自动化测试框架实战开发(一)

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

    说明:该篇博客是博主一字一码编写的,实属不易,请尊重原创,谢谢大家!

    项目源码下载

    目录

    一丶叙述

    二丶接口基础知识

    三丶接口测试工具

    四丶Fiddler的使用

    五丶unittest使用

    六丶mock服务入门到实战

    七丶接口自动化框架设计到开发


    一丶叙述

    1.项目介绍

    整个项目分为四个部分:接口基础丶接口开发丶Unittest与接口测试结合以及接口自动化框架从设计到开发

    接口基础包括:HTTP接口 / 常见接口 / 接口工具 / 接口基础知识

    接口开发:通过Django来开发get/post接口

    Unittest与接口测试结合:unittest应用 / 断言 / requests引入 / HTMLTestRunner / case的管理

    接口自动化框架从设计到开发:如何设计框架 / 封装工具类 / 重构基类 / 错误调试 / 结果收集以及处理 / 解决数据依赖 / 结果统计及报告发送

    项目整体思路:通过对接口数据文档的读写操作,来获取文档中case的所有数据,然后通过requests模块来发送请求获取的响应数据,通过返回的响应数据中的某个标志性字段的值来判断是否测试成功或者失败,最后将测试的结果数据写入到测试文档或者是html页面又或者是将结果以邮件的形式发送到指定邮箱,这是整个大框架思路,要完成这一系列自动化的测试框架,则需要有一定的python代码基础,博主这里只是粗略的叙述了思路,有很多地方就不细说了比如数据依赖等就请大家慢慢的阅读吧

    2.测试报告效果预览

    • unittest和HTMLTestRunner结合生成报告(新版本的)

    •  unittest和HTMLTestRunner结合生成报告(经典版本的)

    •  测试报告邮件通知

    二丶接口基础知识

    1.什么是接口

    连接前后端以及移动端,通俗来说就是前端和后端之间的桥梁,比如网站需要去调用银行丶微信及支付宝的接口来完成业务需求

    2.接口的种类

    外部接口和内部接口;内部接口又分为上层服务与下层服务以及同级服务

    3.接口的分类

    请求方式:post丶get丶delete丶put

    4.为什么要做接口测试

    原因:不同端的工作进度肯定是不一致的,那么就需要对最开始开发出来的接口进行测试;对于项目来说缩短项目周期,提高开发效率以及提高系统的健壮性

    5.接口测试流程

    需求讨论——需求评审——场景设计——用例设计——数据准备——执行

    6.为什么要设计测试用例

    • 理清思路,避免侧漏
    • 提高测试效率
    • 跟进测试进度
    • 告诉领导做过
    • 跟进重复重复性工作

    7.用例设计分类

    功能用例测试:测试功能是否正常丶测试功能是否按照接口文档实现

    逻辑用例设计:是否存在依赖业务,例如有些操作是需要用户登录成功的状态下才能进行的操作

    异常测试用例设计:参数异常以及数据异常;参数异常包括关键字参数丶参数为空丶多参数丶少参数丶错误参数,数据异常包括关键字数据丶数据为空丶长度不一致丶错误数据

    安全测试用例设计:cookie丶header丶唯一识别码

    三丶接口测试工具

    1.接口测试工具分类

    • 抓取接口工具

    httpwatch:集成于IE和Firefox浏览器中,在其他浏览器中无法使用,查看数据也比较麻烦

    wireshark:只要是经过电脑的所有请求都会去抓取,导致数据量比较庞大,看数据也比较麻烦

    fiddler:轻量级抓包工具,功能比较全,只会记录http请求不会像wireshark工具记录tcp和udp等请求

    • 测试接口工具:

    loadrunner:不仅仅是性能测试工具,由于该工具几乎都是基于http请求,所以也可以用来测试接口

    fiddler:它除了可以抓包还可以向接口发送各种请求

    soapui:接口和自动化测试工具,功能也比较强大

    jmeter:跟loadrunner一样不仅仅是做性能测试,也可以对接口进行测试

    postman:谷歌旗下的接口测试工具

    四丶Fiddler的使用

    1.抓取不同类型接口数据(http以及https)

    • 查看windows本机的IP

    • 配置fiddler

    • 需要保证要抓取的手机与电脑保持同一网段,博主这里使用逍遥模拟器模拟安卓手机,修改手机网络

    • 在高级选项中设置手动代理IP为windows本机IP地址,端口设置与fiddler抓取端口保持一致

    • 再安卓手机中打开知乎app,抓取知乎app的http服务的数据

    • 现在的移动app都是基于https请求的,所以需要在fiddler中设置https请求

    • 然后在手机端浏览器中访问windows电脑IP+port,进行网络安全证书的下载安装

    • 点击下面一个下载证书

    • 然后设置密码即可

    • 证书安装成功后,重新打开知乎app,则成功抓取https请求的数据

    • 在知乎app中随便对一文章进行评论,抓取该app评论接口

    2.数据模拟以及过滤规则

    • 如下图进行选择要过滤的hosts类型,并在输入框添加要过滤的hosts即可

    • 对知乎上的一篇文章进行回答后,获取https://api.zhihu.com/answers接口,查看发送的post请求数据中的content字段内容也就是博主回答的内容

    • 然后进行数据模拟,也就是点击fiddler软件上的replay对https://api.zhihu.com/answers接口进行post请求数据的而二次发送,由于知乎这边设定对一个问题只能进行一次回答,所以知乎服务器返回的json数据提示我们失败,同时也说明对接口进行二次数据发送成功,只是规则逻辑失败

    3.如何模拟接口响应数据

    • 首先第一步,访问知乎app热榜,在fiddler软件中获取接口查看服务器响应的json格式数据,从服务器返回的json数据看出热榜标题字段名为title_area

    • 然后选择服务器返回的数据类型为TextView,点击.View in Notepad即打开数据记事本,如下图在记事本中找到title_area字段的内容,该字段内容进行了将中文转换为一串字符串

    • 将记事本中的title_area字段的数据修改为this is a test for cdtaogang

    • 点击文件——另存为保存到桌面 

    • 回到fiddler中,左侧选中热榜接口,右侧选中AutoResponder,在此窗口下点击Add Rule将左侧的接口添加进去,在右侧下方导入保存在桌面的zhihu_hot.htm文件,最后点击sava保存

    • 回到知乎app中刷新当前热榜页面,则成功返回修改的热榜标题

    4.使用fiddler进行评论接口测试

    • 对一篇文章进行评论,抓取评论接口,因为get请求的接口测试太简单,所以博主这里选择评论接口即POST请求方式

    • 右击评论接口选择copy复制接口的url地址

    • 右侧选择Composer,将复制的评论接口url粘贴到地址栏,并选择POST请求方式

    • 因为评论接口涉及到用户身份验证也就是登录后才能进行评论的,所以需要将comments接口中request headers请求头中的所有请求数据以及请求数据中的TextView的值进行复制

    请求头数据

    请求体数据

    • 将上面复制的请求头和请求体数据分别粘贴到如下输入框中,点击Execute执行发送,然后在左侧则出现了另一个comments接口数据

    • 查看该comments接口,服务器返回的响应数据中与第一个comments接口一致,说明接口测试成功

    五丶unittest使用

    1.unittest简单使用

    • 在IDE中使用python的环境随便创建个py文件,需要注意的是该py文件的名字不能是test.py,否在运行时会出错,unittest包是python自带的不需要下载安装,代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/17 13:10'
    
    import unittest
    
    
    class TestMethod(unittest.TestCase):
    
        @classmethod
        def setUpClass(cls):
            print("Method before class execution")
    
        @classmethod
        def tearDownClass(cls):
            print("Method after class execution")
    
        def setUp(self):
            print("------setUp------")
    
        def tearDown(self):
            print("------tearDown------")
    
        def test_01(self):
            print("First test method")
    
        def test_02(self):
            print("The second test method")
    
    
    if __name__ == '__main__':
        unittest.main()
    • 直接run运行以上代码

    2.unittest和request重构封装

    说明:使用requests模块对接口url地址发送请求,通过unittest测试框架进行case测试

    • 首先博主在逍遥安卓模拟器中下载了一个看书app,通过fiddler对app上的某一接口进行获取,之所以选择对此app进行接口测试,是因为该app的所有接口全是POST请求

    • 在PyCharm下新建工程目录,目录下创建base包,在包下创建一个demo.py文件以及test_method.py文件,用于使用unittest框架来测试以上app接口

    • 在demo.py文件中,使用requests get以及post方法进行了封装,主要是根据传递的参数method来对get以及post方法进行分别调用而已,具体实现如下
    import requests
    
    
    class RunMain:
    	def send_get(self,url,data):
    		res = requests.get(url=url,data=data).json()
    		return res
    		
    	def send_post(self,url,data):
    		res = requests.post(url=url,data=data).json()
    		return res
    
    	def run_main(self,url,method,data=None):
    		res = None
    		if method == 'GET':
    			res = self.send_get(url,data)
    		else:
    			res = self.send_post(url,data)
    		return res
    • 在test_method.py文件中则创建测试类以及test方法,在test方法中调用demo.py中的run_main方法,即使用requests模块向传递的接口url地址和请求方式以及请求体发送对应的请求,这里使用setUp方法则是利用其优先调用而对RunMain类进行实例化
    import unittest
    import json
    import HtmlTestRunner
    from .demo import RunMain
    
    
    class TestMethod(unittest.TestCase):
        def setUp(self):
            self.run = RunMain()
    
        def test_01(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
                "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
                "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
                "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
                "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
                "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
            }
            res1 = self.run.run_main(url, "POST", json.dumps(data))
            print(res1)
    
    
        def test_02(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
    
            }
            res2 = self.run.run_main(url, 'POST', data)
    
            print(res2)
    
    
    if __name__ == '__main__':
        unittest.main()
    • 运行test_method模块,查看测试接口,test_02则是错误测试

    3.unittest中assert的使用

    • 首先根据返回的结果字典dict数据中的status状态值来判断测试是否通过或者失败,逻辑很基础就不细说了
    class TestMethod(unittest.TestCase):
        def setUp(self):
            self.run = RunMain()
    
        def test_01(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
                "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
                "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
                "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
                "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
                "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
            }
            res1 = self.run.run_main(url, "POST", json.dumps(data))
            # print(type(res1))
            # print(res1['pub'])
            # print(type(res1['pub']))
            if res1['pub']['status'] == 0:
                print("测试通过")
            else:
                print("测试失败")
            print(res1)
    
    
        def test_02(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
    
            }
            res2 = self.run.run_main(url, 'POST', data)
            if res2['pub']['status'] == 0:
                print("测试通过")
            else:
                print("测试失败")
            print(res2)
    
    
    if __name__ == '__main__':
        unittest.main()
    • 运行以上代码,查看结果与预期一样

    • 将if判断代码更换成unittest模块中的assert断言进行判断,这里使用assertEqual方法来判断两个值是否相等,当两个值相等则返回OK,当不相同时返回assertEqual方法msg变量自定义的值
    class TestMethod(unittest.TestCase):
        def setUp(self):
            self.run = RunMain()
    
        def test_01(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
                "sstoken":"eyJleHAiOjE1Njg1MzgyNTczMzUsImlhdCI6MTU2MDc2MjI1NzMzNSwicHAiOiIxMTQwNTQ1Njg5MDYwMDQ0ODAwQHNvaHUuY29tIiwidGsiOiIwZkNYSHpjTUZzR0dFMEswbVdvUVFCNWVCanpXa0hmWiIsInYiOjB9.SDYkT9FpWrBbko6xRrESN74IXJhzkqQLtijKjGiVrqA",
                "gidinf":"x011060802ff0fd40695d68140002799751474c540b3",
                "ppinf":"2|1560762257|1561971857|bG9naW5pZDowOnx1c2VyaWQ6Mjg6MTE0MDU0NTY4OTA2MDA0NDgwMEBzb2h1LmNvbXxzZXJ2aWNldXNlOjMwOjAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMHxjcnQ6MTA6MjAxOS0wNi0xN3xlbXQ6MTowfGFwcGlkOjY6MTEwNjA4fHRydXN0OjE6MXxwYXJ0bmVyaWQ6MTowfHJlbGF0aW9uOjA6fHV1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1aWQ6MTY6czk1YWIwNDk5NjE3YmJhNnx1bmlxbmFtZTowOnw",
                "pprdig":"kaKPdU0WwIdzL58CqxNz5pgMyv23P0-Y5GRnd5ufPlXIGzrk7_7TlIK5XFQiuoqAHNqGVXHCVd4cB1DIkR5yFZ_nExnSjIZbBJWYlMkrsiIjDYqWCvedZRLm8sZqS0WqA0FcKXuSn3Z0gVRus9YpEonNz5wyuWdUqxaSmzlzygY",
                "ppsmu":"1|1560762257|1561971857|dXNlcmlkOjI4OjExNDA1NDU2ODkwNjAwNDQ4MDBAc29odS5jb218dWlkOjA6fHV1aWQ6MDo|byWcaoPqy02s2_9GHLhZFAQ6Ov_GazMPFLrq115HiSTBS9Ijr33a55quRq2Mr1_6ZMruKEk-BYFpShUaMtwRYA"
            }
            res1 = self.run.run_main(url, "POST", json.dumps(data))
            # print(type(res1))
            # print(res1['pub'])
            # print(type(res1['pub']))
            # if res1['pub']['status'] == 0:
            #     print("测试通过")
            # else:
            #     print("测试失败")
            self.assertEqual(res1['pub']['status'], 0, "测试失败")
            print(res1)
    
    
        def test_02(self):
            url = 'http://api.ishugui.com/asg/portal/call/265.do'
            data = {
    
            }
            res2 = self.run.run_main(url, 'POST', data)
            # if res2['pub']['status'] == 0:
            #     print("测试通过")
            # else:
            #     print("测试失败")
            self.assertEqual(res2['pub']['status'], 0, "测试失败")
            print(res2)
    
    
    if __name__ == '__main__':
        unittest.main()
    • 测试查看结果,断言失败,测试结果如下很清晰

    4.unittest中case的管理及运用

    • 在测试一些接口时,有些接口的返回数据需要在下一个接口进行使用,所以需要定义全局变量,方便每个case都能够得着,当在test_01中定义全局变量userid,然后在test_02中进行打印

    • 在unittest中,是按照字母数字来进行case先后执行顺序的,将test_01改为test_03后,运行代码后,会提示test_02中的userid未定义,原因是程序先去执行了test_02这个case,所以出现该提示是正常的

    • 当在测试代码中有很多case时,我想跳过某个case,则在该case方法上定义unittest的skip方法装饰器,并需要传递此方法名作为实参进行传递

    • 除了在if __name__ == '__main__'中使用unittest.main方法执行所有的case以外,还可以将要测试的case添加到unittest.TestSuite集合中执行想要执行的case,若想要全部都执行则需要一个一个的添加

    5.unittest和HTMLTestRunner结合生成报告(博主这里给大家展现两种)

    第一种:比较新版本的htmltestrunner报告

    • 然后将下载好的whl文件放在你的项目环境的Scripts目录下

    • 最后在Terminal终端或者cmd终端中进入以上目录,执行如下命令即可

    • 安装成功后,即在以下路径中可以找到安装的HTMLTestRunner的包了

    • 在if __name__ == '__main__'中只需要调用HtmlTestRunner模块中的HtmlTestRunner类,向该类传递报告标题参数值即可,其他均默认,需要注意的时启动文件run为当前的py文件,如果是Unittests开头的启动文件,则不会运行if __name__ == '__main__'下的代码,只会执行unittest框架的setUp以及test开头的case代码

    • 运行test_method.py文件,成功在base目录下创建reports目录,并在该目录下生成对应时间的测试报告

    • 打开reports目录下生成的html测试报告,查看测试内容,与预期设定一样,test_02失败test_03成功,说明一下报告中的乱码为中文

    第二种:比较经典版本的htmltestrunner报告

    • 为了方便演示效果,博主在testItems项目目录下,创建base2的模块,将base模块下的demo.py和test_method.py文件拷贝到base2目录下并将test_method.py命令为test_method2.py免得搞混淆,然后在base2目录下新建HTMLTestRunner.py文件用于存放其源码,目录结构如下

    第94行, 将import StringIO修改成import io
    第539行,将self.outputBuffer = StringIO.StringIO()修改成self.outputBuffer = io.StringIO()
    第642行,将if not rmap.has_key(cls):修改成if not cls in rmap:
    第631行,将print >> sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改成print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
    第766行,将uo = o.decode('latin-1')修改成uo = e
    第775行,将ue = e.decode('latin-1')修改成ue = e
    • 在test_method2模块中首先需要从base2模块中去导入HTMLTestRunner文件,然后if __name__ == '__main__'中,需要创建一个文件源,同样是调用HTMLTestRunner模块中的HTMLTestRunner类,不同的是需要将创建的文件源传递给实例属性stream变量

    • 运行test_method2.py,成功在上一级report目录下生成html_report.html报告文件

    • 打开html_report.html测试报告,测试结果与代码设定一致

    六丶mock服务入门到实战

    1.mock简介

    mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法,mock是在测试过程中,对于一些不容易构造/获取的对象,创建一个mock对象来模拟对象的行为即就是模拟fiddler返回接口响应数据的一个过程。

    2.mock安装

    • 在终端使用pip进行安装即可

    3.在case中通过底层函数实现mock

    • 在test_method模块中导入mock,然后在test_03函数中通过以下代码设置返回的return_value的值为请求的data数据
    mock_data = mock.Mock(return_value=data)
    print(mock_data)
    • run运行Unittests in test_method.py,打印出Mock id的值

    • 将调用run_main方法的值设定为mock_data,即print(res1)则表示打印请求的data数据的值,因为res1的数据不再是接口返回的响应数据,则arrest断言是会提示报错的,这是正常的

    4.重构封装mock服务

    • 在base目录下创建mock_demo.py文件,构造一个mock_test方法,该方法就是将test_03方法中self.run.run_main = mock.Mock(return_value=data) 和 res1 = self.run.run_main(url, "POST", json.dumps(data))方法的调用进行了封装成为test_02和test_03方法通用的一个方法,上一步骤中的代码mock_data = mock.Mock(return_value=data) 和self.run.run_main = mock_data,即就相当于self.run.run_main = mock.Mock(return_value=data)而已,都是python基本的调用封装基础知识,mock_demo.py中的代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/20 16:26'
    from mock import mock
    import json
    
    def mock_test(mock_method, request_data, url, method, response_data):
        """
        :param mock_method:
        :param request_data:
        :param url:
        :param method:
        :param response_data:
        :return: res
        """
        mock_method = mock.Mock(return_value=response_data)
        print('mock_method:', mock_method)
        res = mock_method(url, method, json.dumps(request_data))
        return res
    • 那么在test_03方法中,如下进行调用即可
    res1 = mock_test(self.run.run_main, data, url, 'POST', 'ssssssss')
    print('res1:', res1)
    • 运行Unittests in test_method.py,查看运行结果和博主设定一样成功返回自定义的response_data数据

    七丶接口自动化框架设计到开发

    1.如何设计一个接口自动化测试框架

    根据接口地址丶接口类型丶请求数据丶预期结果来进行设计,对于需要登录后才能进行操作的接口那么则需要进行header cookie等数据的传递,自动化测试的难点就是数据依赖。

    2.python操作excel获得内容

    • 首先python操作excel,需要安装两个包,分别是xlrd和xlwt这两个库,xlrd这个库是负责读取excel数据的,而xlwt库是负责向excel写入数据的

    • 在项目目录下创建utils工具包,在该包下创建op_excel.py文件,在该文件中通过导入xlrd包,对excel表的数据进行读取操作

    3.重构操作excel函数

    • 根据上一步骤读取excel表的内容代码后,进行了一个简单的封装,提高代码的通用性,过程相当的简单,实现代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/20 17:33'
    import xlrd
    
    data = xlrd.open_workbook("../test_data/rs.xls")
    tables = data.sheets()[0]  # 获取表格数据对象
    print(tables.nrows) # 打印表格行数
    print(tables.cell_value(0,0))  # 打印excel表格数据,需要传递数据所在的坐标(x,y)
    print(tables.cell_value(0,1))
    print("*"*50+"封装前后数据对比"+"*"*50)
    
    
    class operationExcel(object):
        def __init__(self, file_path="../test_data/rs.xls", sheet_id=0):
            self.file_path = file_path
            self.sheet_id = sheet_id
            self.data = self.get_data()
    
        def get_data(self):
            data = xlrd.open_workbook(self.file_path)
            tables = data.sheets()[self.sheet_id]
            return tables
    
        def get_rows(self):
            """获取单元格的排数"""
            return self.data.nrows
    
        def get_cell_value(self, x=0, y=0):
            """获取某个单元格的数据"""
            return self.data.cell_value(x, y)
    
    
    if __name__ == '__main__':
        print(operationExcel().get_rows())
        print(operationExcel().get_cell_value())
        print(operationExcel().get_cell_value(0,1))
    • 运行op_excel.py文件后,结果与封装之前代码结果一致,表示重构封装代码成功

    4.学习操作json文件

    • 自定义一个登录的json文件名为login.json,文件内容如下,存放在test_data目录下

    • 在utils工具包下创建op_json.py文件,在文件中对login.json文件内容进行读取操作,代码如下

    5.重构json工具类

    • 将上一步操作json的代码进行封装
    class operationJson(object):
        def __init__(self, file_path="../test_data/login.json"):
            self.file_path = file_path
            self.data = self.get_data()
    
        def get_data(self):
            with open(self.file_path) as f:
                data = json.load(f)
                return data
    
        def get_key_words(self, key=None):
            if key:
                return self.data[key]
            else:
                return self.data
    
    
    if __name__ == '__main__':
        print(operationJson().get_key_words())
        print(operationJson().get_key_words("login"))
        print(operationJson().get_key_words("login")['username'])
    • 运行op_json.py文件,结果与封装之前代码结果一致,表示重构封装代码成功

    6.封装获取常量方法

    • 首先打开excel表格,查看需要获取的字段有哪些

    • 对excel表的字段进行获取,在项目目录下创建名为data的python包,在该包下创建data_conf.py,代码就是简单的获取对应的变量值,具体如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/21 9:29'
    
    
    class global_var:
        id = '0'  # id
        module = '1'  # 模块
        url = '2'  # url
        run = '3'  # 是否运行
        request_type = '4'  # 请求类型
        request_header = '5'  # 是否携带header
        case_depend = '6'  # case依赖
        response_data_depend = '7'  # 依赖的返回数据
        data_depend = '8'  #  数据依赖
        request_data = '9'  # 请求数据
        expect_result = '10'  # 预期结果
        reality_result = '11'  # 实际结果
    
    
    def get_id():
        return global_var.id
    
    def get_module():
        return global_var.module
    
    def get_url():
        return global_var.url
    
    def get_run():
        return global_var.run
    
    def get_request_type():
        return global_var.request_type
    
    def get_request_header():
        return global_var.request_header
    
    def get_case_depend():
        return global_var.case_depend
    
    def get_response_data_depend():
        return global_var.response_data_depend
    
    def get_data_depend():
        return global_var.data_depend
    
    def get_request_data():
        return global_var.request_data
    
    def get_expect_result():
        return global_var.expect_result
    
    def get_reality_result():
        return global_var.reality_result

    7.封装获取接口数据

    • 在data目录下创建data_get.py文件,在该文件中对excel表数据以及json数据结合上一步封装的常量方法整合后的实现,代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/21 10:01'
    from utils.op_excel import operationExcel
    from utils.op_json import operationJson
    from data import data_conf
    
    class getData(object):
        def __init__(self):
            self.op_excel = operationExcel()
    
        def get_case_lines(self):
            """获取表格行数"""
            return self.op_excel.get_rows()
    
        def get_is_run(self, x):
            """获取case是否运行"""
            flag = None
            y = data_conf.get_run()
            run_value = self.op_excel.get_cell_value(x, y)
            if run_value == 'yes':
                flag = True
            else:
                flag = False
            return flag
    
        def get_is_header(self, x):
            """是否携带header"""
            y = data_conf.get_request_header()
            header = self.op_excel.get_cell_value(x, y)
            if header == 'yes':
                return data_conf.get_header_value()
            else:
                return None
    
        def get_request_method(self, x):
            """获取请求方式"""
            y = data_conf.get_request_type()
            request_method = self.op_excel.get_cell_value(x, y)
            return request_method
    
        def get_request_url(self, x):
            """获取请求地址"""
            y = data_conf.get_url()
            request_url = self.op_excel.get_cell_value(x, y)
            return request_url
    
        def get_request_data(self, x):
            """获取请求数据"""
            y = data_conf.get_request_data()
            request_data = self.op_excel.get_cell_value(x, y)
            if request_data == '':
                return None
            return request_data
    
        def get_data_for_json(self, x):
            """通过excel中的关键字去获取json数据"""
            op_json = operationJson()
            data = op_json.get_key_words(self.get_request_data(x))
            return data
    
        def get_expect_data(self, x):
            """获取预期结果数据"""
            y = data_conf.get_expect_result()
            expect_data = self.op_excel.get_cell_value(x, y)
            if expect_data == '':
                return None
            return expect_data

    8.post、get基类的封装

    • 在base包下创建run_method.py文件,在文件中重新编写对get丶post请求方式的代码封装,具体如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/21 11:19'
    import requests
    
    
    class RunMain(object):
    
        def get_main(self, url, data=None, header=None):
            res = None
            if header is not None:
                res = requests.get(url=url, data=data, headers=header).json()
            else:
                res = requests.get(url=url, data=data).json()
            return res
    
        def post_main(self, url, data, header=None):
            res = None
            if header is not None:
                res = requests.post(url=url, data=data, headers=header).json()
            else:
                res = requests.post(url=url, data=data).json()
            return res
    
    
        def run_main(self, url, method, data=None, header=None):
            res = None
            if method.lower() == 'post':
                res = self.post_main(url, data, header)
            elif method.lower() == 'get':
                res = self.get_main(url, data, header)
            else:
                return "what ?????"
            return res

    9.主流程封装及错误解决调试

    • 首先在testItems项目目录下新建一个名为main的python包,在该包下创建名为run_test的py文件,该文件为主程序启动文件,代码的逻辑就是将前面封装的方法进行了调用核心就是读取excel表的数据,通过读取到的数据,发送请求,其中包括某一些变量的判断,根据该判断然后到json数据中获取请求的数据,最后就这么的简单,代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/21 11:57'
    from base.run_method import RunMain
    from data.data_get import getData
    
    
    class RunTest(object):
        def __init__(self):
            self.runmain = RunMain()
            self.data = getData()
    
        def run(self):
            res = None
            row_counts = self.data.get_case_lines()  # 获取excel表格行数
            # print(row_counts) 5
            for row_count in range(1, row_counts):
                # print(row_count) 1,2,3,4
                url = self.data.get_request_url(row_count)  # y行不变遍历获取x列的请求地址
                method = self.data.get_request_method(row_count)  # y行不变遍历获取x列的请求方式
                is_run = self.data.get_is_run(row_count)  # y行不变遍历获取x列的是否运行
                data = self.data.get_data_for_json(row_count)  # y行不变遍历获取x列的请求数据,这里面时三次调用,依次分别是get_data_for_json丶get_key_words丶get_request_data
                header = self.data.get_is_header(row_count)
                print('url:', url)
                print('method:', method)
                print('is_run:', is_run)
                print('data:', data)
                print('header:', header)
    
                if is_run:
                    res = self.runmain.run_main(url,method,data,header)
                    print("*"*60+"分割线"+"*"*60)
            return res
    
    
    if __name__ == '__main__':
        print('res:', RunTest().run())
    • 运行run_test,成功的将excel以及json数据正确打印出来,返回res服务器返回结果,需要说明的是excel表中的所有数据都不是真实存在的,包括json文档数据也是,这里主要是测试整个框架的正确性读取excel以及json文档数据,并正确的发送请求获得相应数据

    • 运行结果出现红色的内容,是由requests模块发送请求的安全请求警告,如不想显示此警告,可以在run_method.py发送请求核心代码进行禁用,禁用代码如下

    • 重新运行run_test,安全请求警告不再显示

    • 根据代码运行结果,对比excel表以及json数据文档内容,数据正确无误

    10. 返回数据格式处理以及调错

    • 为了测试返回的接口的响应数据,博主这里在excel文档以及json文档中添加了一条数据

    • 因为在excel文档中小说的接口不携带header所以在向接口发送请求数据核心代码块,进行了如下修改,因为在excel文档中的最后一个接口时真实的,所以只需要对最后一个接口url返回的字典类型的响应数据进行转换成json格式的数据,并按照关键字进行排序

    • 运行run_test,在最后一个接口中成功打印出我们想要的数据

    11.获取接口返回状态

    • 在发送请求数据核心代码中,进行打印返回的状态码status_code即可,最后一个接口比较特殊,返回的响应数据中没有status_code,所以需要对返回的json数据中的status进行判断,并向其返回数据中添加我们所要的status_code的值

    • 运行代码,当返回的状态码为404表示接口不存在,只要是存在响应数据,则status_code为200,必须说明一点的就是status_code为200不一定表示接口存在,有些大型网站会对其域名下不存在的接口返回200的错误页面显示,所以在测试文档中会体现预期结果和实际结果两项数据需要一致才能表示测试通过

    12.通过预期结果判断case是否执行成功

    • 进行接下来的测试,博主这里重新准备了另一个excel表来进行测试,需要对json文件中的数据进行添加,在excel表Book-05中的请求数据book5关键字对应的json文件的数据故意为空,可以对测试结果有一个对比

    • 在数据获取核心类中定义了一个方法来获取excel表模块字段的数据

    • 回过头在启动文件中获取模块名预期结果并进行打印

    • 运行启动文件,查看运行结果

    • 在utils目录下,创建common_util.py文件,在该文件代码中通过启动文件传递过来的数据来判断excel表预期结果数据的status状态码与res结果中的status状态码是否一致,一致表示测试通过,不一致则失败,代码如下
    # -*- coding: utf-8 -*-
    __author__ = 'cdtaogang'
    __date__ = '2019/6/21 18:43'
    import json
    
    class CommonUtil(object):
        def is_contains(self, expect, reality):
            flag = None
            reality = json.loads(reality)
            expect = json.loads(expect)
            if expect['status'] == reality['pub']['status']:
                flag = True
            else:
                flag = False
            return flag
    • 在启动文件中需要注释掉res响应数据,利于查看测试结果,还需要在启动文件调用is_contains方法来根据其返回值判断测试是否通过

    • 运行启动文件,查看测试结果

    13.将测试结果写入到excel中

    • 首先在op_excel.py中定义一个方法,该方法实现读取excel的数据,并进行copy复制,然后再write方法将数据写入到坐标位置

    • 然后在data_get.py中需要定义一个方法,在该方法中核心逻辑为获取y坐标的值

    • 最后在启动文件中,调用data_get模块中的write_reality_data方法,并将剩余的x坐标的值以及data数据传递给最终的核心方法write_reality_result_data来完成对excel表中的实际结果数据的写入

    • 将excel表进行关闭后,运行启动文件,再次打开excel表,实际结果数据写入正确,之所以需要关闭excel是避免提示提示错误,无法写入保存数据

     

    展开全文
  • 在这个自动化测试框架中。 在config目录中存放的是测试配置相关的文件,配置文件可以使用ini、xml、yml等文件类型。例如,要测试的网址、调试日志的文件名、日志的输出格式等 在data目录中存放的是需要测试的数据。...
    
        ad1.jpg
    

    全栈工程师开发手册 (作者:栾鹏)
    python教程全解

    需要的环境

    • 浏览器(Firefox/Chrome/IE…)
    • Python
    • Selenium
    • Selenium IDE(如果用Firefox)
    • FireBug、FirePath(如果用Firefox)
    • chromedriver、IEDriverServer、phantomjs.exe
    • IDE(Pycharm/Sublime/Eclipse…)

    1、浏览器建议用Firefox或Chrome,千万不要用最新版本,要用早两到三个版本的。
    2、Python不要使用python2,而使用python3。
    3、Selenium安装使用命令pip install selenium
    4、Selenium IDE可以录制回放,只能应用与Firefox浏览器。作为Firefox插件插件而存在。
    5、如果要使用Firefox,必备的插件就是FireBug和FirePath,这俩都可以在附加组件管理器中搜到。
    6、果需要使用Chrome浏览器或者IE浏览器,则需要对应的驱动,下载链接如下:
    chromedriver,chromedriver没有64位版本,32即可驱动:
    http://chromedriver.storage.googleapis.com/index.html

    IEDriverServer,下面链接能够下载所有版本的selenium以及IEDriverServer,IEDriverServer区分32位/64位:
    http://selenium-release.storage.googleapis.com/index.html

    选择合适的版本并下载即可。

    找个容易找到的文件夹放起来,在启动chrome浏览器以及IE时需要用到。

    注意:chromedriver、IEDriverServer等浏览器测试驱动都是调用系统的谷歌浏览器和IE浏览器,所以驱动和浏览器客户端必须相互匹配(而最新的测试驱动往往比最新的浏览器差了好几个版本)。所以在下载驱动时尽量下载最新的,在下载浏览器时要尽量使用旧一点的版本。

    Selenium Webdriver下载链接

    模块名称 模块描述 Selenium Webdriver下载链接
    Selenium Standalone Server 3.0 这是Selenium Webdriver的最新稳定版本。你要执行remote Selenium Webdriver时需要它。同时,注意Selenium 3.0+不再支持RC API。你应该用一个备用接口来启动那些旧的东西 Selenium Webdriver 3.0下载(稳定版本)
    Selenium Java 包(3.0.1)、Selenium Python 包(3.0.0) 这些包包括了一系列的扩展Selenium功能的库 Selenium Java 包 3.0.1(稳定版本)Selenium Python 包 3.0.0(稳定版本)
    IE Server Driver(2.53.1) 如果你想要启动IE来做网页测试,你必须有这两个驱动之一。根据你的系统架构来选择。 32-位 IE Server Driver(稳定版本)64-位 IE Server Driver(稳定版本)
    GECKO Driver(最新版) 这个驱动是用来支持新版本的Firefox浏览器,从这里下载最新版 Mozilla GECKO Driver(稳定版本)
    Google Chrome Driver(最新版) 从这里下载最新版本的Google Chrome驱动 Google Chrome Driver(稳定版本)

    Selenium安装链接(谷歌浏览器)

    https://chrome.google.com/webstore/detail/selenium-ide/mooikfkahbdckldjjndioackbalphokd/related

    YAML文件语法

    YAML 是专门用来写配置文件的语言,非常简洁和强大,远比 JSON 格式方便。它的语法规则可以参考:http://blog.csdn.net/luanpeng825485697/article/details/79478338

    GIitHub托管

    自动化测试的架构代码托管在github上,读者可以自行下载
    https://github.com/626626cdllp/Test/tree/master/Test_framework

    自动化测试框架

    这里写图片描述

    在这个自动化测试框架中。

    • 在config目录中存放的是测试配置相关的文件,配置文件可以使用ini、xml、yml等文件类型。例如,要测试的网址、调试日志的文件名、日志的输出格式等

    • 在data目录中存放的是需要测试的数据。可以使用xmls、xml等文件类型。例如,测试网址中要提交的各种各样的内容。

    • 在drivers目录中存放的是测试需要用到的浏览器驱动。主要为chromedriver.exe、IEDriverServer.exe、phantomjs.exe

    • 在log目录下存放输出日志.log文件。

    • 在report目录下存放测试报告文件html类的文件。

    • 在test目录下存放所有测试相关的文件。

    – 在test/case目录下,用于存放测试用例。

    – 在test/common目录下,用于存放跟项目、页面无关的封装。

    – 在test/interface目录下,用于存放以前台角色测试后台接口的测试用例。

    – 在test/page目录下,用于存放具体页面测试时的重复性过程。

    – 在test/suite目录下,用于存放测试套件,用来组织用例。

    • 在utils目录下存放公共方法。

    – utils/assertion.py文件用于添加各种自定义的断言(测试结果和目标结果是否一致的判断),断言失败抛出AssertionError就OK。

    – utils/client.py文件用于测试web后台接口的前端client,对于HTTP接口添加HTTPClient,发送http请求。还可以封装TCPClient,用来进行tcp链接,测试socket接口等等。

    – utils/config.py文件用于项目公共内容配置,以及读取配置文件中的配置。这里配置文件用的yaml,也可用其他如XML,INI等,需在file_reader中添加相应的Reader进行处理。

    – utils/extractor.py文件用于抽取器,从响应结果中抽取部分数据,这里实现的是json返回数据的抽取,可以自己添加XML格式、普通字符串格式、Header的抽取器

    – utils/file_reader.py文件用于文件的读取,包含配置文件和数据文件的读取函数.根据文件地址,返回文件中包含的内容

    – utils/generator.py文件用于一些生成器方法,生成随机数,手机号,以及连续数字等,以便使用这些数据进行测试

    – utils/HTMLTestRunner.py是一个第三方模块,用于生成html的测试报告。读者可以不改动它。

    – utils/log.py文件通过读取配置文件,定义日志级别、日志文件名、日志格式等。

    – utils/mail.py文件用来给指定用户发送邮件。可指定多个收件人,可带附件。

    – utils/support.py文件用来编写一些支持方法,比如签名、加密等

    相关提示:

    pycharm中如果无法引入自定义模块,要先在pycharm中右键点击项目根目录->标记目录为Resource Root,然后再右键点击项目根目录->根源。这样就能引用项目根目录下的所有自定义模块了。

    在这里插入图片描述

    购买文章,更多优惠,免费送视频课

    展开全文
  • 在做Android自动化测试框架开发之前,我们应该先了解目前的发展状况,知道我们的目标是什么,也要了解当前的主流框架,做到知己知彼,取长补短。 首先要思考和了解几个问题: 我们开发自动化框架的目标是什么?如何...
  • 基于Python的HTTP接口自动化测试框架实现

    万次阅读 多人点赞 2011-04-22 14:36:00
    基于Python的HTTP接口自动化测试框架实现 作者:张元礼 http://blog.csdn.net/vincetest 一、测试需求描述 对服务后台一系列的http接口功能测试。 输入:根据接口描述构造不同的参数输入值 输出:XML文件 eg:...
  • 自动化 测试 框架 开源 测试自动化框架是一组最佳实践,通用工具和库,可帮助质量保证测试... 尽管团队可以构建复杂的自动化测试框架,但是当他们可以使用现有的开源工具,库和测试框架获得相同甚至更好的结果时,通...
  • 课程目标:掌握robotframework自动化测试框架的语法,能够利用robotframework实现自动化测试。 适用人群:已在测试领域,需要向自动化方向进阶编程基础薄弱,想要通过robotframework实现自动化的小伙伴 课程...
  • Jmeter接口自动化测试框架是我自己写的文档,很适合想入门接口自动化的人,该文档很详细,没有一点跳步,值得想入门的人看
  • 基于Espresso和Dagger的自动化测试框架  测试框架可以使用Android推荐的Espresso. 模拟数据可以使用Dagger2, 一种依赖注入框架.Dagger2没有使用反射, 而是使用预生成代码, 提高执行速度.  基于Espresso和Dagger的...
  • JSON Wire协议是一个抽象规范,定义了用户在自动化脚本里操作该如何映射到Selenium或HTTP请求和响应中,通过JSON Wire协议可以在HTTP服务器之间传输信息,Language Bindings和WebDriver就是通过HTTP协议传输JSON数据...
  • 5种比较常见的自动化测试框架

    千次阅读 2020-10-26 14:34:43
    自动化测试框架
  • LazyAndroid是为了解决安卓UI自动化测试中存在的测试工具学习成本高、测试编码中花费在元素查找中的时间过长等问题而诞生的一款UI自动化测试框架。它基于appium,封装了appiumDriver的设置、安卓基本控件的使用和...
  • 自动化测试框架概念

    千次阅读 2016-07-20 16:32:25
    1、自动化测试框架的产生 2、自动化测试框架常见的4种模式: 3、自动化测试的作用 4、自动化测试框架的设计核心思想
  • Python自动化测试框架

    千次阅读 2019-10-27 14:02:15
    Python自动化测试框架 1 什么自动化测试框架 什么是工具——工具(Tools)类似框架,只是工具屏蔽了底层的代码,提供单独的操作界面,以此供用户操作,比如:Selenium IDE、Postman、QTP、Jmeter、LoadRunner等自动...
  • 自动化测试框架总结

    2019-03-19 18:05:21
    自动化测试框架流程图: 自动化测试框架模块: 配置文件读取模块 日志模块 测试数据读取模块 测试执行模块 测试结果更新模块 异常处理模块 屏幕截图模块 自动化框架模块功能说明: 配置文件读取模块 ...
  •  正如第一篇《自己动手写Web自动化测试框架(1):概述》 中提到的,我们要做的是一个简单的自动化测试框架,没有Ajax,没有框架,没有Windows对话框,我们舍弃这些较为复杂的功能,目的就在于,我们想要把注意力...
  • 主流自动化测试框架

    千次阅读 2019-06-21 10:27:41
    三、主流自动化测试框架 桌面程序的工具有:QTP、 AutoRunner web应用的工具有:QTP、AutoRunner、Robot Framework、watir、selenium 手机App自动化测试:后续再分享 由于B/S架构的诸多优势,早几年前大量C/S架构的...
  • 该文档是基于python的自动化测试框架的搭建,详细介绍了框架的具体信息以及各个模块的含义,对于最终搭建该框架起到重要作用。
  • requests 实现的 api 自动化测试框架,只需要配置api对应的url ,request_method,既可以实现api的 自动化测试,并输出详细的测试报告
  • 从框架学习的前提条件到移动端测试框架的设计与实现,一步步从零带着学习者一点点搭建移动端自动化框架,真正掌握移动端测试框架的应用。数据驱动测试、单元测试框架、Page Object Model等技术点,均在课程中体现。
  • 随着技术的进步和自动化技术的出现,市面上出现了一些自动化测试框架,你可以使用这些框架,在调整特定测试适用性和效率参数后,测试你项目的任何模块。这节省了时间,而且由于这些框架被广泛使用,它们非常稳健,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 21,488
精华内容 8,595
关键字:

自动化测试框架