精华内容
下载资源
问答
  • Python自动化接口测试框架(一)前言框架结构整体的流程封装get和post请求RunAll 类自动生成报告自动发邮件 前言 发布博客只是记录下自己对自动化测试框架的一个经历过程,框架整体是有Python+unittest框架完成的,...

    前言

    发布博客只是记录下自己对自动化测试框架的一个经历过程,框架整体是有Python+unittest框架完成的,一开始也是网上看看别人的框架代码,然后下载下来一脸懵,各种报错,不知道从哪开始,逐渐也是一一解析,最后完成了适合自己的自动化框架。
    注:框架部分引用于互联网分享,若有雷同,就是我抄袭的╭(╯^╰)╮

    框架结构

    在这里插入图片描述
    common:是一些公共的封装方法
    HttpTestReport:是存放自动生成的html测试报告
    Log:日志及日志存放
    Testcase:测试脚本
    TtestFile:测试用例.xml存放位置

    整体的流程

    1.config.ini中配置相关属性,由common内各种方法调用,
    2.手工按模板编写测试用例(一般情况下不需要修改脚本,只需要修改脚本数量与用例数量符合)
    3.runall调用封装好的方法,自动调取测试用例文件给测试脚本
    4.自动生成测试报告
    5.自动发邮件到qq邮箱。

    封装get和post请求

    代码片.

    # -- coding: utf-8 --
    import requests
    import json
    
    
    class Webrequests():
        def get(self,url,data,headers):     
            try:
                r = requests.get(url,params=data,headers=headers)
                r.encoding = 'utf-8'   
                json_r = r.json()
                print("Test执行结果:",json_r)
                return json_r
            except BaseException as e:
                print("请求失败!",str(e))
        def post(self,url,data,headers):    
            try:
                r = requests.post(url,data=data,headers=headers)
                r.encoding = 'utf-8'
                json_r = r.json()
                print("Test执行结果:",json_r)
                return json_r
            except BaseException as e:
                print("请求失败!",str(e))
        def post_json(self,url,data,headers):   
            try:
                data = json.dumps(data)  
                r = requests.post(url,data=data,headers=headers)
                r.encoding = 'utf-8'
                json_r = r.json()
                print("Test执行结果:",json_r)
                return json_r
            except BaseException as e:
                print("请求失败!",str(e))
    
        def run_main(self, method,url,data,headers):
            result = None
            if method == 'post':
                result = self.post(url,data,headers)
            elif method == 'get':
                result = self.get(url,data,headers)
            elif method == 'post_json':
                result = self.post_json(url, data, headers)
            else:
                print("method值错误!!!")
            return result
    

    外界调用的时候,直接调取run_main方法,传相关参数

    第一章先写到这里吧,后期逐渐更新,给大家看下运行的run类,以及生成的报告

    RunAll 类

    class AllTest:
        def __init__(self):
            log.info("*********测试执行开始*********")
        def run(self):
            try:
                case_dir = os.getcwd() + '\Testcase'       
                testcase = unittest.TestSuite()  
                discover = unittest.defaultTestLoader.discover(case_dir, pattern='test*.py', top_level_dir=None)
                for test_suit in discover:
                    for test_case in test_suit:
                        testcase.addTest(test_case)
                report_path = os.getcwd() + '\HttpTestReport'
                now = time.strftime('%Y-%m-%d %H%M%S')
                fp = open(os.getcwd() + '\HttpTestReport\HttpTest' + now + '.html', 'wb')
                runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u'自动化测试报告', description=u'测试用例执行情况:', tester=u"测试员")
                runner.run(testcase)
                fp.close()
                print("测试报告生成成功,请查收···")
                print("%s" % report_path)
                log.info("测试报告生成成功,请查收···")
                log.info("%s" % report_path)
                email_name = re.findall(r"name='(.+?)'>",str(fp))
                report_name = email_name[0][-30:]
                if on_off == 'on':
                    send_mail.send(email_name[0],report_name)
                    log.info("测试报告邮件已发送成功,请查收···")
                else:
                    log.info("测试报告邮件发送失败···")
            except Exception as ex:
                print("测试报告生成失败。错误内容:%s"%ex)
    
    

    自动生成报告

    在这里插入图片描述
    这是由HTMLTestRunner生成的报告,这是优化过得,汉化显示,也方便阅读美观。

    自动发邮件

    在这里插入图片描述
    附带了html的测试报告

    我不是大佬,只是一个努力钻研问题的小萌新,大家如果感兴趣,我会继续后面分享具体的设计步骤以及思路。

    展开全文
  • 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是避免提示提示错误,无法写入保存数据

     

    展开全文
  • python接口自动化测试框架---包括请求的封装、数据库操作、多断言、ddt数据驱动、多种请求方式等
  • 自己写的接口自动化测试大概的框架,可发送简单的测试报告,还需调整。
  • 完整的框架源码下载...一、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试一下

    展开全文
  • 接口测试框架的价值2.框架设计思路3.unittest框架使用原理及语法规则3.自主研发测试框架 1.接口测试框架的价值 1.效率 实现对所有测试脚本、测试数据文件以及测试报告文件的管理,提升接口测试执行和回归的效率 2....

    1.接口测试框架的价值

    1.效率
    实现对所有测试脚本、测试数据文件以及测试报告文件的管理,提升接口测试执行和回归的效率
    2.成本
    降低人工的工作成本,可以通过框架自动来自动运行,提高人工的产能
    3.复用
    框架可以应对多种不同的接口测试工作的需求,适用性和扩展性强
    4.规范
    对接口测试成功物进行规范化管理,方便随时进行工作的开展以及成果物的查阅

    2.框架设计思路

    框架设计目标:最终只需要调整框架配置文件的一些参数,不需要人工干预测试脚本的执行
    框架设计的过程
    1.框架的层次确定下来:

    1.配置层:框架的配置文件---csv
    2.脚本层:独立接口脚本,联调的接口脚本
    3.数据层:独立接口数据,联调接口数据
    4.报告层:联调接口测试报告,框架测试报告(此次框架执行过程中,运行了哪些接口测试脚本?)
    

    2.对框架的设计进行评审
    3.创建框架对应的文件夹(框架分层):
    框架分层

    4.把现有相关的成果物,放入对应的文件夹,命名尽量有一个统一的规范:

    脚本成果物(独立接口测试脚本,联调接口测试脚本)
    测试数据文件
    测试报告文件
    框架配置文件
    

    5.框架的设计
    6.框架的实现
    7.框架的调试运行

    测试框架执行原理
    测试框架执行原理
    Unittest测试框架设计原理
    Unittest框架设计原理

    3.unittest框架使用原理及语法规则

    案例演示V1.0(简单的框架脚本封装实现):通过unittest框架实现注册接口测试

    2.设计:定义测试类,必须继承unittest类

    class 测试类名(unittest.TestCase):
    

    语法步骤
    1.导入unittest库

    import	unittest
    

    2.定义测试类,必须继承unittest.TestCase

    class RegisterTest(unittest.TestCase):
    

    3.测试方法名字必须以test_开头

    def test_register(self):
    

    4.在main函数中直接使用unittest.main()即可

    if __name__ == '__main__':
    unittest.main()
    

    5.加入断言:assert断言函数,响应结果是json格式需进行类型转换

    self.assertIn("用户名已经存在", str(response))
    

    6.通过使用setUp进行测试的初始化工作,如果多个测试方法用的都是同一套数据的话,就可以在一个setUp里写

    def setUp(self):
       self.url ="http:/xxxxxxxxxxx/user/register.do"
        self.userinfo = {"username": "程勇1",
                         "password": "123456",
                         "email": "chengyong1@abcd.com",
                         "phone": "13825465786",
                         "question": "最喜欢的书",
                         "answer": "幸福人生"}
    

    完整代码演示

    # 定义测试类,继承unittest框架
    import unittest
    
    import requests
    
    
    class RegisterTest(unittest.TestCase):
        # 使用setUp方法完成接口测试的初始化工作
        def setUp(self):
            self.url = "http:/xxxxxxxxxxx/user/register.do"
            self.userinfo = {"username": "程勇1",
                             "password": "123456",
                             "email": "chengyong1@abcd.com",
                             "phone": "13825465786",
                             "question": "最喜欢的书",
                             "answer": "幸福人生"}
        # 定义unittest测试方法
        def test_register(self):
    
            # 发送接口请求
            s = requests.session()
            response = s.post(self.url, self.userinfo).json()
            print(response)
            # 把json格式的响应结果转换成字符串
            # result = str(response)
            # msg = result.find("用户名已经存在")
            # if msg > 0:
            #     print("注册接口测试通过")
            # else:
            #     print("注册接口测试失败")
            # 现在使用unittest框架断言来进行结果判断
            self.assertIn("用户名已经存在", str(response))
    
    
    if __name__ == '__main__':
        unittest.main()
    

    案例演示V1.2(简单的框架脚本封装实现):通过unittest框架实现检查用户名或邮件是否有效接口测试

    7.通过使用teardown进行测试的回收工作(setUp打开文件,teardown关闭文件)
    完整代码演示:

    # coding:utf8
    # 实验二:针对检查接口通过unittest框架进行脚本实现
    # 导入相关库文件
    # 定义测试类,继承unittest框架
    import csv
    import unittest
    
    import requests
    
    
    class checktest(unittest.TestCase):
        # 使用setUp进行接口测试初始化
        def setUp(self):
            self.file1 = open("../testdatafile/ind_interface/check_test_data.csv", "r")
            self.file2 = open("../testresultfile/ind_interface/check_result.csv", "w")
            self.url = "http://xxxxxxxxxx/user/check_vaild.do"
        # 定义接口测试方法
        def test_check(self):
            # 从csv文件中读取测试数据
            checkinfo = {}
    
            table = csv.reader(self.file1)
            for row in table:
                checkinfo["str"] = row[0]
                checkinfo["type"] = row[1]
                print(row[0])
                s = requests.session()
                response = s.post(self.url, data=checkinfo).text
                print(response)
                # 原来是用if判断添加断言,现在用assert添加断言
                self.assertIn(row[2], str(response))
    
        # 通过teardown方法进行回收工作
        def tearDown(self):
            self.file1.close()
            self.file2.close()
    
    
    if __name__ == '__main__':
        unittest.main()
    

    案例演示V1.3(多个接口联调测试):通过unittest框架实现多个接口的联调测试------注册接口,登录接口

    实现步骤
    1.导入unittest框架
    2.定义测试类-----指定一个测试类实现多个测试方法
    3.定义setUp进行初始化
    4.定义test方法
    5.使用测试套进行测试用例的执行
    6.使用文件加载的方式进行测试用例的执行

    思考:框架如何进行测试方法执行顺序控制?
    1.如果框架中只有一个方法:先执行setUp,然后再执行测试方法test_xxx,最后才执行tearDown
    2.如果框架中有多个方法:是按照asc码顺序执行的(方法名字母顺序a–z),执行顺序如下:

    setUp
    测试方法test_case1
    tearDown
    setUp
    测试方法test_case2
    tearDown
    ....
    

    使用测试套进行测试用例的执行步骤
    1.在main里面声明测试套对象

    suite = unittest.TestSuite()
    

    2.把需要执行的测试用例用addTest方法加入测试套中
    由addTest的顺序来进行测试方法执行顺序的控制

    suite.addTest(测试类的名称("test_xxx"))
    

    3.声明框架运行的对象

    runner = unittest.TextTestRunner()
    

    4.通过runner对象执行测试套件

    runner.run(suite)
    
    if __name__ == '__main__':
        # 声明测试套对象
        suite = unittest.TestSuite()
        # 调用addTest方法调用类里面的方法就把类写再前面,再写入方法名
        suite.addTest(mulinterfacetest("test_case2"))
        suite.addTest(mulinterfacetest("test_case1"))
        # 声明测试运行对象
        runner = unittest.TextTestRunner()
        runner.run(suite)
    

    测试框架之unittest.main()运行原理
    在main里面执行测试方法无法控制执行顺序,所有的用例方法只要没有注释都是会全部执行

    if __name__ 'main':
    unittest.main()
    

    在这里插入图片描述

    测试框架之unittest.TestSuite()运行原理
    如果想指定执行某一个测试用例就用TestSuite测试套来执行
    在这里插入图片描述

    使用文件加载的方式进行测试用例的执行
    执行一些或一个python的测试文件

    实现步骤:
    1.声明文件路径
    2.通过调用unittest对应的discower方法打开对应的测试文件
    3.声明一个runner的对象
    4.执行指定的测试文件

    # 以文件的方式来进行测试框架的执行
        # 声明文件所在路径
        testdir = './'  # 这里是路径
        discower = unittest.defaultTestLoader.discover(testdir, pattern="test*.py")
        # 声明测试运行对象
        runner = unittest.TextTestRunner()
        runner.run(discower)
    

    小结:
    unittest框架的使用要求:
    1.导入

    import  unittest
    

    2.定义测试类需要继承unittest

    class 测试类名(unittest.TestCase)
    

    unittest测试框架的基本组成要素
    1.setUp初始化方法-------这个方法是可选的,可以有也可以没有,这个是测试前期的初始化工作
    2.以test_打头的测试方法
    3.tearDown方法资源回收------可选,一般用来关闭一些对象释放对象或者关闭一些文件
    4.运行方式
    unitest框架的执行顺序:
    setUp进行初始化
    执行测试方法
    tearDown进行资源回收
    三种不同的框架运行方式的对比:
    unittest.main()好处: 特点:只能执行当前类中对应的所有测试方法
    1.依次执行当前文件中对应测试类的所有测试方法
    2.执行顺序以asc码顺序
    3.一般的测试方法名称建议定义为:test_case1,test_case2…

    测试套件的执行: 特点:按照加载顺序的顺序和个数进行执行
    注意:运行模式要改为以python文件而不是测试框架模式运行
    在这里插入图片描述

    1.声明一个测试套

    	suite = unittest.TestSuite()
    

    2.通过addTest方法添加测试用例------对应的测试方法

    suite.addTest(类名("测试方法名"))
    

    3.声明一个测试运行对象

    runner  = unittest.TextTestRunner()
    

    4.执行测试套

    runner.run(suite)
    

    测试文件执行: 特点:执行任意指定路径下的任意相关命名的python测试脚本,执行每一个文件中所有的测试方法,顺序也是按照asc码顺序进行
    1.声明文件所在路径
    2.定义discower对象打开相关的脚本文件

    discower = unittest.defaultTestloder.discower(文件路径,pattern=“test_*.py”)
    

    3.声明测试运行对象

    runner = unittest.TexttestRunner()
    

    4.执行测试套

    runner.run(discower)
    

    3.自主研发测试框架

    需求原型
    1.框架目录结构,一般的设计思路:配置层,脚本层,报告层,驱动层;这里的分层没有加入驱动层

    框架分层

    2.框架各层需要完成的工作:
    1.配置层:config,不同人设计的配置层相关内容是不一样的,这里设计的配置层里面的放的是csv文件,由配置文件来控制此次测试执行需要调用哪些测试脚本

    2.脚本层:script, ind_interface:存放独立接口测试脚本,表示一个类中有一个测试方法,完成一个接口的测试;mul_interface:存放联调接口测试脚本,一个类中有多个测试方法,完成接口与联调的测试
    注意:脚本文件的名称要有一定的规范性,此次规定测试脚本文件以test_开头

    3.测试数据文件层:testdatafile,ind_interface:存放独立接口测试脚本对应的测试数据文件;mul_interface:存放的是联调接口测试脚本对应的测试数据文件

    4.测试报告文件层:testresultfile,framresult:测试框架报告;ind_interface:存放独立接口测试报告;mul_interface:存放联调接口测试报告

    5.框架驱动层:test_driver:存放测试框架的驱动程序

    框架的测试执行过程:
    先由框架驱动层中的框架驱动程序运行,依据配置层相关的设置调用对应的脚本层的程序进行执行,相关的脚本运行时,如果需要测试数据,则在数据层进行脚本文件的读取,测试脚本执行结束后,会写明相关的测试报告文件,并存入测试报告层

    测试框架前期的准备工作:
    1.依据框架需求搭建框架项目及模块
    2.接口测试数据文件向框架转移

    框架分层目录结构图:这里加入驱动层
    在这里插入图片描述

    框架测试脚本的研发:
    1.重构测试脚本:案例演示:更新用户信息接口测试
    实现步骤:
    1.在框架对应的分层下创建新的python文件(注意:符合命名规范)
    2.按照unittest框架的思想进行脚本设计

    一般来讲不需要这么多版本同时存在,看实际需求,如果1.0够用就直接可以测试执行了,若感觉1.0不充分就注释掉或者是分成不同得测试方法,还是在一个文件里写;这里采用的是用不同的测试方法实现不同版本的测试
    V1.0
    1.导入unittest
    2.定义一个类,继承unittest
    3.传入固定的接口测试数据(一组)
    4.assert进行判断
    V2.0
    4.是否需要测试数据文件
    V3.0
    5.加入测试报告文件

    3.测试脚本的实现:V1.0

    脚本演示

    # coding:utf8
    #*********************************************************************
    # 对更新用户信息的接口进行测试,使用unittest框架技术
    # 接口说明
    # 接口访问地址:
        # http://xxxxxxx/update_infomation.do
    # 接口传入参数:
        # 1.email  2.phone 3.question 4.answer
    # 接口预期返回值
        # 1.email已存在,请更换email再尝试更新 2.更新个人信息成功 3.更新个人信息失败"
    #************************************************************************
    # 脚本实现:
    # 导入相关的库
    # 定义测试类,继承unittest框架
    import unittest
    import requests
    
    class updateuser_test(unittest.TestCase):
        # V1.0版本,传入一组固定的接口测试数据,进行接口测试
        def test_case1(self):
            # 传入指定的接口测试数据
            url = "http://xxxxxxx/user/update_information.do"
            userinfo = {"email": "xiugaiyonghu@qq.com",
                        "phone": "13898768578",
                        "question": "最喜欢的书",
                        "answer": "十万个为什么"}
            # 进行接口调用
            response = requests.post(url, data=userinfo).text
            print(response)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    运行结果

    Ran 1 test in 0.032s
    
    OK
    {"status":1,"msg":"用户未登录"}
    

    设计问题分析:测试更新用户的接口,需要先进行登录
    测试场景1:未登录,进行更新,提示:用户未登录
    测试场景2:先登录,再进行更新(遇到问题:发送了登录请求,在调用更新接口时还是提示“用户未登录“需要传入sessionID)

    需要解决的问题:解决方案:
    方案1(常用):在当前的测试类中,追加一个setUp方法,登录的测试脚本写入setUp方法中
    方案2:在当前的测试类中,追加一个新得测试方法test_case1(完成登录的调用),把原来的更新测试方法名改为test_case2
    方案3:在当前的测试类及测试方法中,前面追加一段代码,完成登录的调用
    方案四:另外创建一个脚本文件,来实现登录,通过测试框架进行接口测试联调

    脚本传入sessionID的解决办法:
    先用接口测试工具看一下接口响应里的JSESSION字典,在登录测试方法里获取到,并转换成字典,再写上字典的key,作为更新用户的传参使用
    V1.0完整脚本案例

    # coding:gbk
    #*********************************************************************
    # 对更新用户信息的接口进行测试,使用unittest框架技术
    # 接口说明
    # 接口访问地址:
        # http://localhost:8888/jwshoplogin/user/update_infomation.do
    # 接口传入参数:
        # 1.email  2.phone 3.question 4.answer
    # 接口预期返回值
        # 1.email已存在,请更换email再尝试更新 2.更新个人信息成功 3.更新个人信息失败"
    #************************************************************************
    # 脚本实现:
    # 导入相关的库
    # 定义测试类,继承unittest框架
    import unittest
    import requests
    
    class updateuser_test(unittest.TestCase):
        # 通过setUp方法实现登录接口的调用
        def setUp(self):
            url = "http://xxxxxxx/user/login.do"
            userinfo = {"username": "张三1",
                        "password": "123456"}
            response = requests.post(url, data=userinfo)
            # print(response.text)
            # 获取sessionID转成字典类型写上字典类型的key,并存入一个变量,作为更新用户的的传参
            self.sessionID = dict(response.cookies)['JSESSIONID']
            print(self.sessionID)
        # V1.0版本,传入一组固定的接口测试数据,进行接口测试
        def test_case2(self):
            # 传入指定的接口测试数据
            url = "http://xxxxxxx/user/update_information.do"
            userinfo = {"email": "xiugaiyonghu@qq.com",
                        "phone": "132546677685",
                        "question": "最喜欢的书",
                        "answer": "西游记"}
            # 获取登录的sessionID并传入请求方法中
            session = {'JSESSIONID': self.sessionID}
            print(session)
            # 进行接口调用
            response = requests.post(url, data=userinfo, cookies=session).text
            print(response)
            self.assertIn("更新个人信息成功", response)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    运行结果

    98D0C3CCB24BA805139F3CE4802C9C02
    {'JSESSIONID': '98D0C3CCB24BA805139F3CE4802C9C02'}
    {"status":0,"msg":"更新个人信息成功","data":{"id":38,"
    

    测试脚本实现:V2.0
    1.是否需要测试数据文件:对测试数据文件进行设计(测试用例的设计)
    更新用户数据接口,测试数据分析:

    1.1正常:四组数据:1.email,2.phone,3.answer,3.question;考虑只更新任意一个数据,还是更新多组数据,或是更新全部数据都可以
    1.2异常:考虑:1.未登录,2.邮箱冲突,3.电话的长度不正确,4.问题为空,5.答案为空等等

    测试数据文件
    在这里插入图片描述

    2.把测试数据文件中的内容传入脚本

    案例代码演示

    # v2.通过csv文件进行更新测试数据的读取
    # python文件名update_v2_test.py
    #*********************************************************************
    # 对更新用户信息的接口进行测试,使用unittest框架技术
    # 接口说明
    # 接口访问地址:
        # http://localhost:8888/jwshoplogin/user/update_infomation.do
    # 接口传入参数:
        # 1.email  2.phone 3.question 4.answer
    # 接口预期返回值
        # 1.email已存在,请更换email再尝试更新 2.更新个人信息成功 3.更新个人信息失败"
    #************************************************************************
    import csv
    import os
    import unittest
    
    import requests
    
    
    class Update_v2_test(unittest.TestCase):
        # 通过setUp方法实现登录接口的调用
        def setUp(self):
            # 从测试数据文件中读取url和登录测试数据
            path = os.getcwd()
            # print(path)
            # p1 = os.path.abspath(os.path.dirname(path)+os.path.sep+".")
            # print(p1)
            # 获取当前路径的上两级目录路径
            p2 = os.path.abspath(os.path.dirname(path)+os.path.sep+"..")
            # print(p2)
            # 获取到的上两级目录路径加上csv测试数据所在路径
            self.fpath = p2 + "\\test_data_file\\ind_interface\\updateuser_test_data.csv"
            # print(fpath)
            # self.file = open("../../test_data_file/ind_interface/updateuser_test_data.csv")
            userinfo = {}
            file1 = open(self.fpath, "r")
            table = csv.reader(file1)
            for row in table:
                url = row[0]
                userinfo[row[3]] = row[4]
                userinfo[row[5]] = row[6]
                print(userinfo)
                # 只想打印第一行信息就用break退出循环
                break
            response = requests.post(url, data=userinfo)
            self.sessionID = dict(response.cookies)["JSESSIONID"]
            print(self.sessionID)
    
        # V2.0版本,读取指定测试数据文件中对应的内容,进行接口测试
        def test_case2(self):
            # 打开对应的文件
            file1 = open(self.fpath, "r")
            # 如何从指定行开始进行读取
            table = csv.reader(file1)
            userinfo = {}
            number = 0
            for row in table:
                number += 1
                if number > 1:
                    url = row[0]
                    expresult = row[1]
                    j = int(row[2])
                    # 下标从2开始,j表示有几组数据的下标乘以两组数据加上初始值,最后的2表示步长
                    for i in range(3, j*2+2, 2):
                        userinfo[row[i]] = row[i+1]
                    print(userinfo)
    
                    # 获取登录的sessionID并传入请求方法中
                    session = {'JSESSIONID': self.sessionID}
                    print(session)
                    # 进行接口调用
                    response = requests.post(url, data=userinfo, cookies=session).text
                    print(response)
                    userinfo = {}
                    self.assertIn("更新个人信息成功", response)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    小结:
    注意的事项:
    1.文件位置的读取,获取当前路径首先导包import os,然后写path = os.getcwd()获取上一级路径p1 = os.path.abspath(os.path.dirname(path)+os.path.sep+"."),获取前两级路径在这里插入代码片p2 = os.path.abspath(os.path.dirname(path)+os.path.sep+"..")获取上一级后面写一个点,获取上两级写两个点
    2.数据文件的设计:把固定的内容放在前面的列,把不定向的参数放在后面的列中,通过手工加入参数个数,方便进行循环读取,注意参数的正确性,先设计正确的数据,保证脚本测试通过,再设计错误的数据。
    3.从指定的某一行开始读取内容number =0 for row in table:追加number += 1例如想从第四行读起,前三行都空转过去if num >4

    num = 0
    for row in table:
    	num += 1
    	if num > 4:
    

    测试脚本实现3.0:
    加入测试报告文件:生成HTML格式的测试报告
    步骤:
    1.下载HTMLTestRunner.py

    下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html
    

    2.拷贝到项目文件夹下
    3.导入HTMLReport包

    from HTNLTestRunner import HTMLTestRunner
    

    4.生成测试报告的脚本

    做好测试执行前的准备工作,指定要执行的测试用例
    1.以wb(二进制写文件)模式打开测试报告文件
    2.使用HTMLTestRunner方法创建HTML文件
    3.执行测试
    4.关闭文件
    

    代码演示

    # V3.0 创建HTML格式的测试报告文件
    # python文件名updateuser_v3_test.py
    #*********************************************************************
    # 对更新用户信息的接口进行测试,使用unittest框架技术
    # 接口说明
    # 接口访问地址:
        # http://localhost:8888/jwshoplogin/user/update_infomation.do
    # 接口传入参数:
        # 1.email  2.phone 3.question 4.answer
    # 接口预期返回值
        # 1.email已存在,请更换email再尝试更新 2.更新个人信息成功 3.更新个人信息失败"
    #************************************************************************
    import csv
    import os
    import unittest
    import requests
    from HTMLTestRunner import HTMLTestRunner
    
    class Updateuser_v3_test(unittest.TestCase):
        # 通过setUp方法实现登录接口的调用
        def setUp(self):
            # 从csv测试数据文件中读取url和登录测试数据
            path = os.getcwd()
            # 获取当前路径的上两级路径
            p2 = os.path.abspath(os.path.dirname(path) + os.path.sep+"..")
            # 上两级路径加上测试数据所在路径
            self.fpath = p2 + "\\test_data_file\\ind_interface\\updateuser_test_data.csv"
            userinfo = {}
            file = open(self.fpath, "r")
            table = csv.reader(file)
            for row in table:
                url = row[0]
                userinfo[row[3]] = row[4]
                userinfo[row[5]] = row[6]
                print(userinfo)
                # 只打印第一行登录的测试数据,这里用break退出循环
                break
            response = requests.post(url, data=userinfo)
            # print(response)
            # 获取sessionID转换成字典类型,写上字典的key并存在变量里作作为写一个接口的调用
            self.sessionID = dict(response.cookies)["JSESSIONID"]
            # print(sessionID)
    
        def test_case(self):
            file = open(self.fpath, "r")
            table = csv.reader(file)
            userinfo = {}
            n = 0
            for row in table:
                if n > 0:
                    url = row[0]
                    # 各行数据组的和的下标
                    j = int(row[2])
                    # 下标从3开始,一组是两个数据乘以2加上开始的初始值,步长是2
                    for i in range(3, j*2+3, 2):
                        # i是字典大的key,i+1是字典的值
                        userinfo[row[i]] = row[i + 1]
                    # print(userinfo)
                    # 传入上一个接口的sessionID
                    session = {"JSESSIONID": self.sessionID}
                    # print(session)
                    response = requests.post(url, data=userinfo, cookies=session).text
                    print(response)
                n += 1
    
    
    
    if __name__ == '__main__':
        # unittest.main()
        path = os.getcwd()
        p2 = os.path.abspath(os.path.dirname(path) + os.path.sep + "..")
        # 加载测试套
        suite = unittest.TestSuite()
        suite.addTest(Updateuser_v3_test("test_case"))
        # 定义测试报告文件
        filename = p2 + r"\test_result_file\ind_interface\updateuser_test_report.html"
        # 以wb(二进制写文件的方式)打开文件
        file = open(filename, "wb")
        # 调用HTML测试报告的报告生成测试报告
        runner = HTMLTestRunner(stream=file, title="更新用户接口测试", description="接口测试报告")
        runner.run(suite)
        file.close()
    

    运行结果
    注意:这里测试脚本类文件里面写的测试套,运行的时候需要修改运行方式,给文件改个名字,要么再新建一个文件写main函数调用才可以执行测试套,要不然还是会再框架里面执行就不会生成测试报告

    .<_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> 
    Time Elapsed: 0:00:00.284698
    

    生成的测试报告
    在这里插入图片描述

    小结:
    1.在main函数中生成html格式的测试报告

    1.获取要写入测试报告文件对应位置的路径
    2.给定具体的测试报告文件名
    3.以wb(二进制写入)的方式打开文件
    4.设置测试套,并添加要测试的方法
    5.生成测试报
    runner = HTMLTestRunner(stream=file, title="标题", description="描述")
    6.调用runner对象执行测试套
    runner.run(suite)
    7.关闭测试报告文件
    

    测试框架设计流程图在这里插入图片描述

    4.测试框架驱动程序设计

    驱动程序流程图
    设计及实现框架驱动程序
    V1.0在配置文件中写入一个测试文件进行执行
    1.设计一个配置文件:脚本名称,脚本所在路径
    在这里插入图片描述

    2.读取配置文件的内容
    3.找到对应的脚本文件进行调用

    # 测试框架驱动程序1.0版本,文件名runner_v1_test
    # 只是从配置文件中读取一个脚本文件进行调用
    import unittest
    import csv
    
    if __name__ == '__main__':
        # 指定对应得脚本路径1.0版本;从csv文件中读取相关的路径和文件名
        file = open(r"E:\workspace2\interfaceframe\config\config1.csv")
        table = csv.reader(file)
        num = 0
        for row in table:
            if num > 0:
                testdir = row[0]
                fname = row[1]
                print(testdir, fname)
            num += 1
        # testdir = "E:\workspace2\interfaceframe\script\ind_interface"
        discover = unittest.defaultTestLoader.discover(testdir, pattern=fname)
        # 定义一个运行对象
        runner = unittest.TextTestRunner()
        runner.run(discover)
    

    运行结果

    "status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    {"status":0,"msg":"更新个人信息成功"}
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.411s
    
    OK
    

    小结:
    思路:由简到繁选择一种框架调用模式defaultloader方式,先用常量值进行脚本的调试,路径和脚本名称都是常量,再把常量值改为变量值,逐个替换不要全部替换,再把变量值改为从文件读取

    发现问题,添加print语句进行分析;问题:路径不一致,级别关系发生了变化;解决问题,调整脚本中对应的路径级别

    discover = unittest.defaultTestLoader.discover(路劲, pattern=脚本名称)
    

    V2.0在配置文件中写入两个测试文件进行执行:
    1.修改配置文件
    2.进行脚本的修改

    # 测试框架驱动程序1.0版本,文件名runner_v1_test
    # 只是从配置文件中读取一个脚本文件进行调用
    import unittest
    import csv
    
    if __name__ == '__main__':
        # 指定对应得脚本路径1.0版本;从csv文件中读取相关的路径和文件名
        file = open(r"E:\workspace2\interfaceframe\config\config1.csv")
        table = csv.reader(file)
        num = 0
        for row in table:
            if num > 0:
                testdir = row[0]
                fname = row[1]
                print(testdir, fname)
                # testdir = "E:\workspace2\interfaceframe\script\ind_interface"
                discover = unittest.defaultTestLoader.discover(testdir, pattern=fname)
                # 定义一个运行对象
                runner = unittest.TextTestRunner()
                runner.run(discover)
            num += 1
    

    运行结果

    {'username': '李慧1', 'password': '123456'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data"}
    {"status":0,"msg":"更新个人信息成功","data":}
    {'username': '李慧1', 'password': '123456'}
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.390s
    
    OK
    {'email': 'lihuixiugai1@qq.com'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'phone': '13277777777'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'question': '李慧1问题更新'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'answer': '李慧1答案更新'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'email': 'lihuixiugai2@qq.com', 'phone': '13277777776'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'question': '李慧2问题更新', 'answer': '李慧2答案更新'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'email': 'lihuixiugai3@qq.com', 'phone': '13277777778', 'question': '李慧3问题更新', 'answer': '李慧3答案更新'}
    {'JSESSIONID': '9D27D9C5104EEECEF6D94D07DD7C2A57'}
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.416s
    
    OK
    

    V3.0在配置文件中针对不同的运行状态进行文件的执行:
    1.升级改造配置文件---------加入一列状态列(状态表示0/1;RUN/ONRUN;NO/YES:运行和不运行)
    在这里插入图片描述
    2.脚本的调整--------加入条件判断

    # 测试框架驱动程序1.0版本,文件名runner_v1_test
    # 只是从配置文件中读取一个脚本文件进行调用
    import unittest
    import csv
    
    if __name__ == '__main__':
        # 指定对应得脚本路径1.0版本;从csv文件中读取相关的路径和文件名
        file = open(r"E:\workspace2\interfaceframe\config\config1.csv")
        table = csv.reader(file)
        num = 0
        for row in table:
            if num > 0 and row[2] == "yes":
                testdir = row[0]
                fname = row[1]
                print(testdir, fname)
                # testdir = "E:\workspace2\interfaceframe\script\ind_interface"
                discover = unittest.defaultTestLoader.discover(testdir, pattern=fname)
                # 定义一个运行对象
                runner = unittest.TextTestRunner()
                runner.run(discover)
            num += 1
    

    运行结果

    E:\workspace2\interfaceframe\script\ind_interface updateuser_v2_test.py
    E:\workspace2\interfaceframe\test_data_file\ind_interface\updateuser_test_data.csv
    {'username': '李慧1', 'password': '123456'}
    {'email': 'lihuixiugai1@qq.com'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'phone': '13277777777'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data":{"id":47,"username":"李慧1}
    {'question': '李慧1问题更新'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'answer': '李慧1答案更新'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data":{"id":47,"username":"李慧1}
    {'email': 'lihuixiugai2@qq.com', 'phone': '13277777776'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data":{"id":47,"username":"李慧1}
    {'question': '李慧2问题更新', 'answer': '李慧2答案更新'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    {"status":0,"msg":"更新个人信息成功","data"}
    {'email': 'lihuixiugai3@qq.com', 'phone': '13277777778', 'question': '李慧3问题更新', 'answer': '李慧3答案更新'}
    {'JSESSIONID': 'A7284724F342B08EE928506687CB14A2'}
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.274s
    
    OK
    

    V4.0按照测试人员指定的顺序来执行相应的测试文件:
    1.升级改造配置文件-----------加入执行顺序列
    在这里插入图片描述
    2.修改脚本

    # v4.0完成从配置文件中读取测试脚本,执行状态以及执行顺序
    # 文件名称 runner_v2_test
    # 实验:对数据字典的内容进行排序
    import operator
    # dic = {"testA": 3, "testC": 1, "testB": 4, "testD": 2}
    # dicn = sorted(dic.items(), key=operator.itemgetter(1))
    # print(dicn)
    # for fn in dicn:
    #     print(fn[1])
    
    # 实验:把配置文件中的内容放入字典中
    # import csv
    # 以只读方式打开
    # file = open(r"E:\workspace2\interfaceframe\config\config1.csv")
    # tanle = csv.reader(file)
    # # line = len(open(r"E:\workspace2\interfaceframe\config\config1.csv").readlines())
    # # print(line)
    # dic = {}
    # listd = []
    # line = 0
    # for row in tanle:
    #     # print(row[0])
    #     if line > 0:
    #         dic = {}
    #     # 把文件中读取的数据放入字典
    #         dic[row[1]] = row[0]
    #         dic["num"] = int(row[3])
    #         # print(dic)
    #     line += 1
    #     if dic != {}:
    #         listd.append(dic)
    # print("n,行数", line)
    # # print(listd)
    # dicn = sorted(listd, key=operator.itemgetter("num"))
    # print(dicn)
    # for i in range(0, line-1):
    #     n = 0
    #     for content in dicn[i].items():
    #         if n == 0:
    #             fname = print(content[0])
    #             fdir = print(content[1])
    #             print(fname, fdir)
    #         n += 1
    
    #**********************V4.0驱动程序**************************************************
    import unittest
    import csv
    import operator
    
    
    if __name__ == '__main__':
        # 打开对应的配置文件,进行读取
        # 以只读方式打开
        file = open(r"E:\workspace2\interfaceframe\config\config1.csv")
        tanle = csv.reader(file)
        # line = len(open(r"E:\workspace2\interfaceframe\config\config1.csv").readlines())
        # print(line)
        dic = {}
        listd = []
        line = 0
        for row in tanle:
            # print(row[0])
            if line > 0:
                dic = {}
            # 把文件中读取的数据放入字典
                dic[row[1]] = row[0]
                dic["order"] = int(row[3])
                # print(dic)
            line += 1
            if dic != {}:
                listd.append(dic)
        print("n,行数", line)
        # print(listd)
        dicn = sorted(listd, key=operator.itemgetter("order"))
        print(dicn)
        for i in range(0, line-1):
            n = 0
            for content in dicn[i].items():
                if n == 0:
                    fname = (content[0])
                    fdir = (content[1])
                    print(fname, fdir)
                    # 调用脚本进行执行
                    discover = unittest.defaultTestLoader.discover(fdir, pattern=fname)
                    # 定义一个运行对象
                    runner = unittest.TextTestRunner()
                    runner.run(discover)
                n += 1
    

    V5.0按照测试顺序和是否运行来确定要执行哪些测试:
    复杂的程序编写思路:
    1.分解任务
    2.逐步合并,分段调试
    3.脚本设计思路:

    突破点:python提供的数据字典的排序:
    	脚本名,脚本路路径、执行顺序,是相关的一组数据,不是无关的;import operator  sorted(数据字典,key=operater.itemgetter(下标或标签))
    实验一:给定一些字典的基本数据,排序算法是否可行
    dic = {"testA": 3, "testC": 1, "testB": 4, "testD": 2}
    dicn = sorted(dic.items(), key=operator.itemgetter(1))
    print(dicn) -----排序算是否可行
    实验二:把csv配置文件的一行内容导入字典中
    实验三:把csv配置文件的多行内容导入到字典中
    实验四:检验读取的顺序是否正确,能否正确执行
    实验五:加入对状态的判断
    
    # coding:utf8
    # 把所有配置文件中的内容全部进行读取
    # 文件名:runner_v3_test
    import unittest
    import csv
    import operator
    
    
    if __name__ == '__main__':
        # 打开对应的配置文件,进行读取
        # 以只读方式打开
        file = open(r"E:\workspace2\interfaceframe\config\config1.csv", 'r')
        table = csv.reader(file)
        # line = len(open(r"E:\workspace2\interfaceframe\config\config1.csv").readlines())
        # print(line)
        dic = {}
        listd = []
        line = 0
        for row in table:
            # print(row[0])
            if line > 0:
                # 每一次执行之前清空字典
                dic = {}
            # 把文件中读取的数据放入字典
                dic[row[1]] = row[0]
                dic["order"] = int(row[3])
                # 把脚本的运行状态加入字典数据
                dic["stada"] = row[2]
                # print(dic)
            line += 1
            if dic != {}:
                listd.append(dic)
        print("n,行数", line)
        # print(listd)
        dicn = sorted(listd, key=operator.itemgetter("order"))
        # print(dicn)
        # 从列表字典读取数据,for key,value in dict.items()
    
        for i in range(0, line-1):
            n = 0
            for content in dicn[i].items():
                if n == 0:
                    # print(content)
                    fname = (content[0])
                    fdir = (content[1])
                    # print("文件名", fname, "路径", fdir)
                if n == 2:
                    # print(content)
                    state = content[1]
                    print("state", state)
                    if state == "yes":
                        # 调用脚本进行执行
                        print("最终运行的程序", fname)
                        discover = unittest.defaultTestLoader.discover(fdir, pattern=fname)
                        # 定义一个运行对象
                        runner = unittest.TextTestRunner()
                        runner.run(discover)
    
                n += 1
    
    
    展开全文
  • 本文总结分享介绍接口测试框架开发,环境使用python3+selenium3+unittest+ddt+requests测试框架及ddt数据驱动,采用Excel管理测试用例等集成测试数据功能,以及使用HTMLTestRunner来生成测试报告,目前有开源的poman...
  • 分享做接口测试时,搭建的测试框架思路 python + excel + unittest + requests + logger + database + htmlreport 目前实现功能点: 核心通用测试框 token关联 上个接口结果的值赋予下个接口的请求参数等 日志模块 ...
  • 测试用例写在excel表格里,通过程序实现自动化运行测试用例,将错误日志打印在log里。是根据一个开源项目改了一些代码,正确填好interface测试用例表格之后就能正常运行。
  • 利用下班之后的空余时间和周六周天,通过看博客、搜资料终于把接口自动化测试框架搭建好了,使用python+requests+pytest+yaml+allure实现了接口自动化测试,并进行调试,成功调通公司的项目接口,我是学习并借鉴下面...
  • Python接口自动化测试框架搭建

    千次阅读 2020-06-03 14:22:48
    此框架是基于python的unittest单元测试框架写的。写的有点简单,有些地方可能不是很完整,后期在逐步完善。 先来看下框架的目录布局: common :公共的方法目录 conf: 配置文件存放目录 data :测试数据目录 ...
  • Python接口自动化测试框架httpautotest

    千次阅读 2017-06-27 15:08:33
    写此博客以记录自己的第一个自动化接口测试框架本框架实现如下功能: 支持robotframework 支持POST和GET方法的HTTP接口 纯数据驱动 支持整体返回断言 支持MYSQL数据库查询结果断言 项目已开源,github地址: 框架...
  • python自动化测试框架,使用request,测试数据统一由excel管理,后期维护只需要修改excel中的测试数据,减少的代码的重复改动,提升测试效率
  • 从今天开始给大家介绍一个python接口自动化测试框架,框架是基于Python+unittest+requests+HTMLTestRunner,大家在网上也可以看到这个框架的使用,基于此框架我会稍微做些改动,增加一些比如如何获取token的方法,...
  • 1、测试框架简介 Config目录:存放配置文件,如数据库的端口、地址、邮件配置信息等; Data目录:存放公共动态数据,如Token、Excel、动态参数等; Log目录:存放Log日志信息; Reports目录:存放接口测试报告; ...
  • 基于Python的HTTP接口自动化测试框架实现

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,933
精华内容 773
关键字:

python自动化接口测试框架

python 订阅