美文网首页接口测试Python接口测试实战接口测试
Python接口测试实战5(下) - RESTful、Web S

Python接口测试实战5(下) - RESTful、Web S

作者: 韩志超 | 来源:发表于2018-09-14 20:07 被阅读243次

    课程目录

    Python接口测试实战1(上)- 接口测试理论
    Python接口测试实战1(下)- 接口测试工具的使用
    Python接口测试实战2 - 使用Python发送请求
    Python接口测试实战3(上)- Python操作数据库
    Python接口测试实战3(下)- unittest测试框架
    Python接口测试实战4(上) - 接口测试框架实战
    Python接口测试实战4(下) - 框架完善:用例基类,用例标签,重新运行上次失败用例
    Python接口测试实战5(上) - Git及Jenkins持续集成
    Python接口测试实战5(下) - RESTful、Web Service及Mock Server

    更多学习资料请加QQ群: 822601020获取

    本节内容

    • REST及RESTful API
    • Web Service
    • XML解析
    • Mock Server

    REST及RESTful API

    参考链接: https://blog.csdn.net/lch2848508/article/details/72729658

    REST:表述性状态转移或表现层状态转移,“表现”及每个接口地址(URI)都表现为(视为)一个资源对象(文本资源、图片资源、服务资源),状态转移指通过POST/PUT方法发送完整的新状态信息来更改资源对象的状态
    如某https://api.***.com/user资源状态为{"name": "Kaka", "age": 30},我们通过POST/PUT请求发送新状态{"name": "Kaka", "age": 18}来更新对象信息,完成状态转移

    URI 与URL的区别:URL值包含协议的链接,如https://www.baidu.com, 还有一种相对链接叫URN,如/doc/1.html,这两种都能唯一定位一个资源,URI(统一资源定位符)包含URL和URN

    RESTful API是一种接口设计风格或规范,主要有以下特点:

    • 统一使用https协议
    • 接口使用专用的api域名 https://api.example.com/
    • 接口分版本管理 https://api.example.com/v1/
    • 路径又称"终点"(endpoint),表示API的具体网址,路径中只能包含名词(代表资源对象),可以使用复数来代表多个一个资源集合https://api.example.com/v1/zoos
    • 同一个接口提供多种请求方法,GET获取资源信息,POST新建或更新资源,PUT/PATCH更新资源,DELETE删除资源
    • 可以通过url参数过滤信息 https://api.example.com/v1/zoos?limit=10 # 获取前10个
    • 尽量使用JSON, 避免使用XML
    • 身份认证推荐使用OAuth2.0,或Basic Auth,token等,避免使用Cookie和Session(RESTful强调无状态的设计)

    示例:

    https://api.github.com
    授权 Basic Auth (superhin001, ***) 或 Oauth 2.0 Access Token: 1c4f679300f29ee4e7041028d49e504b9da145b1

    GET https://api.github.com/user 获取用户信息

    GET https://api.github.com/user

    POST/PATCH https://api.github.com/user 修改用户信息

    POST https://api.github.com/user
    POST/PATCH 数据
    {
        "login": "superhin001",
        "id": 21163682,
        "node_id": "MDQ6VXNlcjIxMTYzNjgy",
        "avatar_url": "https://avatars3.githubusercontent.com/u/21163682?v=4",
        "gravatar_id": "",
        "url": "https://api.github.com/users/superhin001",
        "html_url": "https://github.com/superhin001",
        "followers_url": "https://api.github.com/users/superhin001/followers",
        "following_url": "https://api.github.com/users/superhin001/following{/other_user}",
        "gists_url": "https://api.github.com/users/superhin001/gists{/gist_id}",
        "starred_url": "https://api.github.com/users/superhin001/starred{/owner}{/repo}",
        "subscriptions_url": "https://api.github.com/users/superhin001/subscriptions",
        "organizations_url": "https://api.github.com/users/superhin001/orgs",
        "repos_url": "https://api.github.com/users/superhin001/repos",
        "events_url": "https://api.github.com/users/superhin001/events{/privacy}",
        "received_events_url": "https://api.github.com/users/superhin001/received_events",
        "type": "User",
        "site_admin": false,
        "name": "我是韩老师",
        "company": null,
        "blog": "",
        "location": null,
        "email": "superhin@126.com",
        "hireable": null,
        "bio": null,
        "public_repos": 3,
        "public_gists": 0,
        "followers": 0,
        "following": 0,
        "created_at": "2016-08-22T01:12:32Z",
        "updated_at": "2018-09-14T02:33:43Z",
        "private_gists": 0,
        "total_private_repos": 0,
        "owned_private_repos": 0,
        "disk_usage": 45430,
        "collaborators": 0,
        "two_factor_authentication": false,
        "plan": {
            "name": "free",
            "space": 976562499,
            "collaborators": 0,
            "private_repos": 0
        }
    }
    

    GET https://api.github.com/user/keys 获取用户所有SSH-Key信息

    GET https://api.github.com/user/keys

    POST https://api.github.com/user/keys 新建Key

    POST https://api.github.com/user/keys

    POST 数据

    {
            "id": 30742411,
            "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDfsTJs7mNWstJ+tO6O1jQHKdDdnldqlqkO0gAune9EH7oqICD1hP7c1duNZwvNnvyGa7SyqamIpNXmSYv303FEVAXzPsb9MzCChG16gzevQtbIX4Qt7vFOsHNSCikSCD/s6DMa0Koryiu7Yju5mW9UUnjVM+a1P80SOiK7p2UBQPFVKRrUtr0htV3U6a2rdP51Vzm2UCjChTUa4q7L3m4C7oB9aSvUsNTk+PmuJlAer4oOd7FsNPqD1Or3lRKAmgxbTX4xTaOkwibK0t2eYkh/VTUPMQ9wDwpa4hZLiEq9qSew3McCwsl70k4H0H4F/VwV2sSCXqZu274YmNDT5Hl3 hanzhichao@hanzhichao01",
            "title": "test3",
            "verified": true,
            "created_at": "2018-09-14T09:54:51Z",
            "read_only": false
    }
    

    Web Service

    Web Service 是一种跨平台(Java对象,Python也可以调用)RPC(远程方法调用)解决方案。
    基于SOAP协议,使用XML这种跨平台语言传输对远程方法的调用信息及返回结果,并提供WSDL接口描述服务

    • SOAP:简单面向对象协议, 基于XML语言,使用HTTP协议传输
    • XML: 可扩展标记语言,同JSON一样是一种跨平台语言
    • WSDL: Web Service服务描述语言,提供远程对象的调用描述信息(类似于接口文档,XML格式)

    SOAP格式

    SOAP协议基于XML语言, SOAP消息体首先必须有个信封(Enelope),信封中可以有信息头(Header)和信息体(Body),其中Body中还可以包含错误信息(Fault)
    基本格式如下:

    <!-- 信封固定格式 指定命名空间为soapenv -->
    <soapenv:Envelope xmlns:soapenv="http://www.w3.org/2001/12/soap-envelope" soapenv:encodingStyle="http://www.w3.org/2001/12/soap-encoding">   
        <soapenv:Header>  <!--信息头 可选,可写成但标签-->
            ......
        </soapenv:Header>
        <soapenv:Body>   <!--信息体 实际调用内容-->
            ......
        </soapenv:Body>
    </soap:Envelope>
    

    SOAP详细教程:http://www.runoob.com/soap/soap-header.html

    使用SoupUI
    示例接口: http://115.28.108.130:4000/?wsdl
    由于Postman等不具备将wsdl接口信息解析成对象描述的功能,我们使用另一个SOAP接口专用的测试工具SoupUI

    SoupUI下载地址: http://www.wmzhe.com/soft-32815.html

    1. 新建项目


      File -> 新建SOAP项目
    填入项目名,WSDL地址,点击OK 保存后自动解析出所有方法(接口)信息
    1. 发送接口
    填写参数处 填写完参数,点击发送按钮,查看响应结果

    使用Fiddler抓包,查看raw格式:

    POST http://115.28.108.130:4000/ HTTP/1.1
    Accept-Encoding: gzip,deflate
    Content-Type: text/xml;charset=UTF-8
    SOAPAction: "addUser"
    Content-Length: 370
    Host: 115.28.108.130:4000
    Connection: Keep-Alive
    User-Agent: Apache-HttpClient/4.1.1 (java 1.5)
    
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="UserService">
       <soapenv:Header/>
       <soapenv:Body>
          <user:addUser>
             <!--Optional:-->
             <user:name>范冰冰</user:name>
             <!--Optional:-->
             <user:password>123456</user:password>
          </user:addUser>
       </soapenv:Body>
    </soapenv:Envelope>
    

    使用Postman发送SOAP接口

    Postman发送SOAP接口

    text/xml 和 application/xml的区别: 当你<?xml version='1.0' encoding='UTF-8'>指定编码的时候,使用application/xml会安装xml指定的编码传输,而使用text/html会默认使用us-ascii编码编码传输数据

    使用Python操作Web service接口

    pip install suds-jurko

    from suds.client import Client
    
    service = Client("http://115.28.108.130:4000/?wsdl").service  # 获取远端服务对象
    result = service.addUser("范冰冰", "123456")  # 向本地方法一样调用
    print(result)  # 输出 用户已存在
    

    使用requests库发送

    import requests
    
    url = 'http://115.28.108.130:4000/'
    
    data = '''<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:user="UserService">
       <soapenv:Header/>
       <soapenv:Body>
          <user:addUser>
             <!--Optional:-->
             <user:name>张三</user:name>
             <!--Optional:-->
             <user:password>123456</user:password>
          </user:addUser>
       </soapenv:Body>
    </soapenv:Envelope>
    '''.encode('utf-8')
    
    res = requests.post(url=url,data=data)
    print(res.text)
    

    结果:

    <?xml version='1.0' encoding='UTF-8'?>
    <soap11env:Envelope xmlns:soap11env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="UserService"><soap11env:Body><tns:addUserResponse><tns:addUserResult>用户已存在</tns:addUserResult></tns:addUserResponse></soap11env:Body></soap11env:Envelope>
    

    XML解析

    XML: 可扩展标记语言,使用<tag></tag>标签,多级树状结构,多用来存储和传输数据,如:

    <bookstore>
        <book category="COOKING">
            <title lang="en">Everyday Italian</title>
            <author>Giada De Laurentiis</author>
            <year>2005</year>
            <price>30.00</price>
        </book>
        <book category="CHILDREN">
            <title lang="en">Harry Potter</title>
            <author>J K. Rowling</author>
            <year>2005</year>
            <price>29.99</price>
        </book>
        <book category="WEB">
            <title lang="en">Learning XML</title>
            <author>Erik T. Ray</author>
            <year>2003</year>
            <price>39.95</price>
        </book>
    </bookstore>
    
    • 根元素(根节点),父元素,子元素,同级元素:bookstore为根元素,book是title的父元素,title是book的子元素,title和author是同级元素
    • 标签,属性,文本:bookstore boot title等为标签(元素节点),boot中category="COOKING" category为属性,COOKING为属性值,<title></title>之间的Everyday Italian为文本

    Python解析XML

    1. 加载元素树(ElementTree)得到根节点
    2. 从根节点使用xpath查找其他节点
    from xml.etree import ElementTree
    d = '''<bookstore>
        <book category="COOKING">
            <title lang="en">Everyday Italian</title>
            <author>Giada De Laurentiis</author>
            <year>2005</year>
            <price>30.00</price>
        </book>
        <book category="CHILDREN">
            <title lang="en">Harry Potter</title>
            <author>J K. Rowling</author>
            <year>2005</year>
            <price>29.99</price>
        </book>
        <book category="WEB">
            <title lang="en">Learning XML</title>
            <author>Erik T. Ray</author>
            <year>2003</year>
            <price>39.95</price>
        </book>
    </bookstore>
    '''
    root = ElementTree.fromstring(d) # 加载元素树(ElementTree)得到根节点
    
    print(root.find("."))  # 选择当前节点
    print(root.find("book"))  # 选择标签为book的子节点
    print(root.find("book[2]"))  # 选择标签为book的第三个子节点
    print(root.find("book[@category='COOKING']"))  # 选择标签为book切标签属性为category="COOKING"
    print(root.find("./book/[title='Harry Potter']"))  # 选择标签为book的节点中包含子标签title 切title的文本内容为Harry Potter
    

    结果:

    <Element 'bookstore' at 0x000002406B666688>
    <Element 'book' at 0x000002406B6666D8>
    <Element 'book' at 0x000002406B8600E8>
    <Element 'book' at 0x000002406B6666D8>
    <Element 'book' at 0x000002406B8600E8>
    

    find()返回的是节点对象,可以通过.tag获取标签名,.attrib获取属性字典,.text获取文本

    XPath选择器

    • 路径: 按路径选择
    • 标签名: 选定所有该名的标签
    • *: 选定所有元素
    • . : 选择当前节点
    • ..: 选择上级节点
    • //: 选择当前元素下所有级别的元素
    • [@属性名]:选择所有具有当前属性的元素
    • [@属性名=属性值]: 选择属性=属性值的元素
    • [子标签名]:选择具有当前子标签的元素
    • [子标签名='文本']: 选择子标签文本的元素

    常用的三种定位元素方法

    1. 从根节点按路径选择: root.find("./book[3]/title")
    2. 按属性并结合相对路径选择: root.find(book[@category='WEB']/title)
    3. 按所包含的独特子标签选择: root.find(book[title='Harry Potter']/title)

    Mock Server

    Mock 即模拟,就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法,其最大的优势就是降级前后端耦合度,使前端工程师可以不依赖后端返回数据,先开发前端样式以及逻辑处理
    简单来说: Mock是用了解决依赖问题的,将复杂的/不稳定的/还未建立的依赖对象用一个简单的假对象来代替

    Mock Server 即Mock接口服务器,可以通过配置快速Mock出新的接口
    Mock Server的使用范围

    • 前后端分离项目
    • 所测接口依赖第三方系统(还未具备)
    • 所测接口依赖复杂或依赖的接口不稳定,并不作为主要验证对象

    同时在接口还未开发好时,提供Mock接口(假接口)会比只有接口文档更直观,并能有效减少沟通成本和一些文档理解bug

    Postman的Mock Server功能

    New -> Mock Server 添加接口及返回值 新建Mock环境 测试Mock接口

    Postman还可以基于Collection建立Mock Server,这里不再详述

    Python+Flask自己搭建Mock接口
    使用Flask包我们可以快速搭建Mock接口

    pip install flask

    from flask import Flask, request, jsonify, abort
    import random
    
    app = Flask(__name__) # 实例化一个Flask对象
    
    @app.route("/api/user/reg/", methods=["POST"])
    def reg():
        if not request.json or not 'name' in request.json or not 'password' in request.json:
            abort(404) 
        res = [
                  {
                    "code": "100000",
                    "msg": "成功",
                    "data": {
                      "name": "李六",
                      "password": "e10adc3949ba59abbe56e057f20f883e"
                    }
                  },
                  {
                    "code": "100001",
                    "msg": "失败,用户已存在",
                    "data": {
                      "name": "李六",
                      "password": "e10adc3949ba59abbe56e057f20f883e"
                    }
                  },
                  {
                    "code": "100002",
                    "msg": "失败,添加用户失败",
                    "data": {
                      "name": "李六",
                      "password": "e10adc3949ba59abbe56e057f20f883e"
                    }
                  }
              ]
    
        return jsonify(random.choice(res))
    
    if __name__ == '__main__':
        app.run()
    
    使用Mock接口

    相关文章

      网友评论

        本文标题:Python接口测试实战5(下) - RESTful、Web S

        本文链接:https://www.haomeiwen.com/subject/ogqagftx.html