unittest

毕竟地址:http://vip.ytesting.com/newsItemController.do?goContent&id=ff8080816e6c60a5016e79cd38c11d6a

JSON在线格式化: http://tool.oschina.net/codeformat/json

Ur|在线解码: http://tool.chinaz.com/tools/urlencode.aspx

1:什么是HTTP协议

  超文本传输协议 (HyperText Transfer Protocol)

  作用是:规定服务器和浏览器信息传递规范 规定浏览器和服务器以什么格式来交流

  get请求和post请求(请求体数据能跟在url后面的)比如get请求,post请求里面(application/x-www-form-urlencoded)
  传递的一些中文参数都会进行urlencode:url编码的,

2.1.HTTP请求报文介绍
  请求报文格式:
    第一部分,叫请求行。独占一行。由:请求方法+请求路径+协议和协议版本组成。 请求行

    第二部分:从第二行至第N行,叫请求头。由键值对组成。可以有1对,也可以有N对。 请求头(鉴权信息)
          Cookie: 里面有sessionid,和登录有关的
          Content-Type:application/x-www-form-urlencoded; charset=UTF-8 以什么格式传递数据,

            urlencoded:请求的正文是urlencoded格式
          token:token的意思是“令牌”,是服务端生成的一串字符串(这里我们可以随意指定一个字符串), 作为客户端进行请求的一个标识
          token:可以放在请求头里面,也可以放在cookie里面(少数),请求的时候带上token

    第三部分:叫空行,独占一行。由回车换行符组成,作用隔开第二部分和第四部分,因为不知道第二部分到哪里结束

    第四部分:叫请求体或请求正文,一般POST、PUT等方法才有请求体,GET方法没用请求体

  1、2、3是必须的。其中2中必须有Host,其他根据需求。

  总结:四大要素:请求方法,url路径,请求头(有用的头),请求正文

  主要的请求方法有:GET、POST、DELETE 、PUT  等方法----对应增删改查

3:get请求和post请求

  GET方法:获取信息一般使用get
    GET 方法提交数据不安全,数据置于请求行,浏览器地址栏可见;(参数跟在url里面,也就是参数在请求行(地址栏)里面的)
    GET 方法URL长度每个浏览器限制数量不同,最后在2000字符以内(每个浏览器限制都不是一样的)
    GET方法速度快
    浏览器默认的请求方法,不需要提交大量数据、无敏感、保密数据时使用
    可能会被浏览器缓存
    请求可能会被保留在浏览器历史记录中 

  POST方法:提交信息一般使用post
    POST 方法提交数据安全,数据置于消息主体内,浏览器地址栏不可见
    POST 方法提交的数据大小没有限制
    POST 方法速度慢
    需要浏览器指定的,需要提交大量数据、有敏感、保密数据时使用 (用户登录一般是post方法)
    不会被缓存
    请求不会被保留在浏览器历史记录中

4:HTTP响应 

  响应报文格式:
  第一部分:叫状态行。独占一行。由:协议/协议版本、状态码、状态描述符组成

  第二部分:从第二行至第N行,叫响应头。由键值对组成。由N对

  第三部分:叫空行,独占一行。由回车换行符组成

  第四部分:叫响应体。响应回来的数据格式(多种

5:HTTP响应状态码(常见)

  4XX:表示客户端错误,参数写错,或者浏览器错误,
  5XX:服务端的错误,比如服务器gg了,地址不存在,非法请求,数据库挂了
  3XX: 重定向,比如访问百度,http://www.baidu.com如果没有加s会帮我们主动重定向到https://www.baidu.com,抓包可能是302了
  2XX: 请求成功,最常见的是200,
  1XX:服务器收到请求,需要请求者继续执行操作

  200 请求已成功,请求所希望的响应头或数据体将随此响应返回。 
  201 请求已经被实现,而且有一个新的资源已经依据请求的需要而建立, 且其 URI 已经随 Location 头信息返回 
  202 服务器已接受请求,但尚未处理 
  301 (永久移动) 请求的网页已永久移动到新位置。 服务器返回此响应 (对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。 
  302 (临时移动) 服务器目前从不同位置的网页响应请求,但请求者应 继续使用原有位置来进行以后的请求。 
  303 (查看其他位置) 请求者应当对不同的位置使用单独的 GET 请求来 检索响应时,服务器返回此代码。 
  304 (未修改) 自从上次请求后,请求的网页未修改过。 服务器返回此 响应时,不会返回网页内容。 
  305 (使用代理) 请求者只能使用代理访问请求的网页。 如果服务器返 回此响应,还表示请求者应使用代理。 
  307 (临时重定向) 服务器目前从不同位置的网页响应请求,但请求者 应继续使用原有位置来进行以后的请求。 
  401 当前请求需要用户验证。如果当前请求已经包含了 Authorization 证书,那么
  401 响应代表着服务器验证已经拒绝了那些证书

  403 服务器已经理解请求,但是拒绝执行它。与
  401 响应不同的是,身份 验证并不能提供任何帮助,而且这个请求也不应该被重复提交 
  404 请求失败,请求所希望得到的资源未被在服务器上发现 
  500 服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。 一般来说,这个问题都会在服务器的程序码出错时出现。 
  501 服务器不支持当前请求所需要的某个功能。当服务器无法识别请求的 方法,并且无法支持其对任何资源的请求。 
  502 作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收 到无效的响应。 
  503 由于临时的服务器维护或者过载,服务器当前无法处理请求。这个状 况是临时的,并且将在一段时间以后恢复

6:请求/响应头中,重要的参数Content-Type

  参数格式Content-Type参数
  常见的数据类型有:
    application/x-www-form-urlencoded    以“键-值”对的方式组织的数据,一般叫表单格式
    multipart/form-data-           一般需要上传文件的表单则用该类型
    form-data                  对应Content-Type:multipart/form-data 类型,

                          既可以发送键值对也可以进行文件参数传递--文件上传用的,表单上传的
    application/json             JSON 字符串格式提交数据
    xml格式也有,很少

  表单上格式一般是url格式:参数以urlencoded表单格式发送给服务器

  一般表单和json格式很好用,

    信息加密:

    1:https加密 相对安全很多,一般用这个
    2:人为md5加密,简单的能解密,复杂的不好解密很复杂

7:表单格式详解

  [urlencoded格式]:又叫form格式,或者是x-www-form-urlencoded格式。
  口诀:表单格式是由键值对组成。键和值之间用=。多个键值对之间用&(and符号)。例如:name=ZhangSan&age=16

8:JSON格式详解      javascript演变来得,
  【json 格式】:  对象和数组两种形式
    口诀1:JSON有对象、数组两种结构,有字符串、数字、逻辑值、空值四种数据类型
    口诀2:用大括号{}表示对象。对象是由属性组成的。属性是由键值对组成的。键和值之间用冒号隔开。属性之间用逗号隔开。键必须用双引号引起来。
    口诀3:用中括号[] 表示数组。数组由单独的值组成
    口诀4:JSON的灵活就灵活在:JSON可以嵌套

  1.1 什么是JSON
    JSON 是存储和交换文本信息的语法。类似 XML,
    JSON 比 XML 更小、更快,更容易解析
    JSON是独立于语言
    JSON 具有自我描述性,更易理解

  1.2 JSON的两种结构
    对象:用大括号表示,对象由属性组成,属性由键值对组成,键和值对之间用冒号隔开,属性之间用逗号隔开,另外键必须用双引号
      如:{"姓名":"姚明","年龄":38},

    数组:用中括号表示,
      如:["小鸡","小鸭","小狗"]
      如:[{"姓名":"姚明","年龄":38}]

    json里面逻辑值false一定要小写:如 {"smoke":false} false大写就会报错,"false"就是传的字符串,false这样传是传的逻辑值true或者false

  1.3 JSON值的可以是以下几种形式:
    数字(整数、浮点数)         number,可以是整数和小数(浮点数)
    字符串                string 键还是值如果是字符串一定要加双引号,规范就是双引号
    逻辑值                true、false) true、false ,布尔值,true和false一定要小写
    null                   null             null空值也要小写的
    对象                 嵌套对象
    数组                  嵌套数组           嵌套使用很灵活

9:cookie和登录有关的,和记住用户名有关的---通过cookie实现

  token也是和登录有关的,登录有cookie登录也有token登录

10:http请求四大要素:

  1:请求方法
  2:url
  3:请求头
  4:请求体(参数和格式)

  开发人员在接口文档中必须要有上述四大要素

  接口功能测试,就是根据开发提供的接口文档设计不同的测试用例,设计不同的请求体(请求方法,url请求头都是固定的)
    构造不同测试用例来设计接口测试就是设计不同的请求体进行测试

  接口功能测试就是传入不同的请求体参数,判断服务器响应结果是否符合预期

11:tcp:

  数据传输协议,传输层的协议,客户端传输到服务端用什么协议传输的,可靠性行传输,不会丢失,安全,速度慢
  在线视频一般都是udp协议,少两个包不影响视频查看,速度快

12:接口测试就是构造request请求,http请求,浏览器可以自动构建http请求,

    >>> r = requests.get('https://api.github.com/user', auth=('user', 'pass'))
    >>> r.status_code
    200
    >>> r.headers['content-type']
    'application/json; charset=utf8'
    >>> r.encoding
    'utf-8'
    >>> r.text
    u'{"type":"User"...'
    >>> r.json()
    {u'private_gists': 419, u'total_private_repos': 77, ...}

13:接口排错技巧: 

  1:web端操作成功抓包
  2:   代码发包抓包
  3:两次抓包进行比较()
  4:错误的包的头+正确包的参数进行发包请求,这样进行分析是不是成功的,交叉比对方法

14:很多时候你想要发送的数据并非编码为表单形式的。如果你传递一个 string 而不是一个 dict,那么数据会被直接发布出去。

  如果data=字符串,会直接发布出去,如下:使用data参数传递字符串,这个字符串是一格json类型的字符串,直接发送出去

    #案例4:教管系统-新增课程2 json格式新增课程
        url='http://localhost:80/apijson/mgr/sq_mgr/'
        headers={
            'Content-Type':'application/json'
        }
        payload='''{
          "action" : "add_course_json",
          "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"}
        }'''
        payload=payload.encode('utf-8')                                                     #传的中文需要编码一下
        fiddler_porxies={
            "http":"http://127.0.0.1:8888",
            "https":"http://127.0.0.1:8888"
        }
        r=requests.post(url,cookies=login(),headers=headers,data=payload,proxies=fiddler_porxies)
        r.encoding="unicode_escape"
        print(r.text) 
        
    #案例4:教管系统-新增课程2 json格式新增课程        服务器需要接收一个json数据        #这里使用json传递参数会把字典类型的数据封装成json传递
        url='http://localhost:80/apijson/mgr/sq_mgr/'
        headers={
            'Content-Type':'application/json'
        }
        payload={
          "action" : "add_course_json",
          "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"}
        }
        fiddler_porxies={
            "http":"http://127.0.0.1:8888",
            "https":"http://127.0.0.1:8888"
        }
        r=requests.post(url,cookies=login(),headers=headers,json=payload,proxies=fiddler_porxies)
        r.encoding="unicode_escape"
        print(r.text)

15:怎么构造请求体数据(HTTP协议第四部分的内容)

  口诀1:params参数,如果传入的是字典,自动编码为表单。---一般get请求需要 params参数针对get方法
  口诀2:data参数,如果传入的是字典,自动编码为表单。
  口诀3:data参数,如果传入的是字符串,按原格式直接发布出去。
  口诀4:json参数,如果传入的是字典,自动编码为json字符串。(外面的字典和里面嵌套的字典都会编码成json字符串)
  口诀5:json参数,如果传入的是字符串,按原格式基础上添加双引号发布出去。 #很少见,传字符串意义不大

  data参数能满足一切要求,能传表单,能传json字符串,json属于锦上添花的参数

16:requests 库响应消息体四种格式:

  r.text:  文本响应内容       返回字符串类型                获取网页html时用,爬虫,txt文本,
  r.content:字节响应内容       返回字节类型(返回二进制格式)        下载图片或文件时用
  r.json():   Json解码响应内容    返回字典格式                  明确服务器返回json数据才能用,否则报错,json转字典,json格式一般用这个
  r.raw:    原始响应内容        返回原始格式

  r.text     返回       {"retcode": 2, "reason": "同名课程存在"}
  r.content   返回         b'{"retcode": 2, "reason": "\\u540c\\u540d\\u8bfe\\u7a0b\\u5b58\\u5728"}'
  r.json()      返回        {'retcode': 2, 'reason': '同名课程存在'}
  r.raw       返回       <urllib3.response.HTTPResponse object at 0x000002C027D2B2B0>      原始很少用

17:requests发出的请求,fiddler抓包 

  可能requests发出的请求,fiddler抓不到,可以为请求方法添加proxies参数,它的值是一个字典。里面的value是fiddler代理和端口。
    proxies = {
      'http': 'http://127.0.0.1:8888',
      'https': 'http://127.0.0.1:8888',
    }
    requests.你的请求方法('你的请求',data=你的payload, proxies=proxies)

18:案例操作脚本源代码:

    import requests
    #案例1:访问百度首页,get请求
    r = requests.get('http://www.baidu.com/')          #r为响应的Response 对象
    r.encoding="utf-8"                            #设置响应编码
    print(r.text)                            #r.text打印第四部分响应消息体里面的内容      r代表整个(raw)的响应的消息

    #案例5:用户登录
    def login():
        url = 'http://localhost:80/api/mgr/loginReq'
        headers={
            'Content-Type':'application/x-www-form-urlencoded'
        }
        payload={"username":"auto","password":"sdfsdfsdf"}
        reps=requests.post(url,headers=headers,data=payload)
        return reps.cookies

    #案例2:教官系统-列表课程  get请求
    url='http://localhost:80/api/mgr/sq_mgr/?action=list_course&pagenum=1&pagesize=20'
    r=requests.get(url,cookies=login())             #r表示response响应消息
    r.encoding="unicode_escape"
    # print(r.text)                                 #r.text表示响应消息体的文本格式
                                                    #params参数能把字典变成表单格式(字典封装成表单)
                                                    #params参数只有get请求才有的参数
                                                    #params参数只支持字典格式传参(传递字典封装成表单加到get请求的url里面)

    #案例3:教官系统-新增课程  POST请求
    url='http://localhost:80/api/mgr/sq_mgr/'
    headers={
        'Content-Type':'application/x-www-form-urlencoded'              #封装请求头,表单格式
    }
    fiddler_porxies={
        "http":"http://127.0.0.1:8888",
        "https":"http://127.0.0.1:8888"
    }
    payload={
        'action':'add_course',
        'data': '{"name":"初中化学","desc":"初中化学课程","display_idx":"4"}'
    }
    #data里面的数据需要是json格式,所以是字符串类型,需要引号引起来
    #假如'data':{"name":"初中化学","desc":"初中化学课程","display_idx":"4"}  data里面没有加引号,发的是一个字典
    #data参数把字典封装成表单发送出去,不仅仅外面的 大的字典格式化成表单
    #还把嵌套的字典格式化成表单  action=add_course&data=name&data=desc&data=display_idx  这样的请求体发送出去的
    #和接口文档不匹配的,接口文档需要完整的json字符串的形式的,变成表单变成了name&data=desc&data=display_idx这种参数了
    #里面的数据也格式化成表单了,不是我们想要的----报错
    #data不管字典在里面还是外面都会格式化成表单发送http数据,
    r=requests.post(url,cookies=login(),headers=headers,data=payload,proxies=fiddler_porxies)
    r.encoding="unicode_escape"
    print(r.text)

    #案例4:教管系统-新增课程2 json格式新增课程
    url='http://localhost:80/apijson/mgr/sq_mgr/'
    headers={
        'Content-Type':'application/json'
    }
    payload={
      "action" : "add_course_json",
      "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"}
    }
    fiddler_porxies={
        "http":"http://127.0.0.1:8888",
        "https":"http://127.0.0.1:8888"
    }
    r=requests.post(url,cookies=login(),headers=headers,json=payload,proxies=fiddler_porxies)
    r.encoding="unicode_escape"
    print(r.raw)
    print(r.text)
    print(r.json())


    # 案例4:教管系统-新增课程2 json格式新增课程
    url='http://localhost:80/apijson/mgr/sq_mgr/'
    headers={
        'Content-Type':'application/json'
    }
    payload='''{
      "action" : "add_course_json",
      "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"}
    }'''
    payload=payload.encode('utf-8')         #传的中文需要编码一下
    fiddler_porxies={
        "http":"http://127.0.0.1:8888",
        "https":"http://127.0.0.1:8888"
    }
    r=requests.post(url,cookies=login(),headers=headers,data=payload,proxies=fiddler_porxies)
    r.encoding="unicode_escape"
    print(r.text)

19:返回报文第二部分返回头内容的获取

  r.status_code        获取响应状态码,一般不以这个作为请求成功的标准,返回200也不代表请求成功的,登录密码错误返回的也是200,
  r.headers          获取响应头信息,cookie,token等信息
  r.cookies          获取cookie对象,这个对象可以取cookie里面的sessionid
  r.cookies['sessionid']     获取cookie对象里面的sessionid的值,默认是sessionid,可能改了名字,sessionid开发人员定义的
  reps.headers['Set-Cookie'] 获取响应头里面的Set-Cookie信息

  sessionid一般和登录有关

20:项目实战练手  

  1:新建项目

    包和文件夹的差别:有没有init.py文件

    2021-1-14APIAuto     项目根目录
      data        文件夹,存放excel自动化测试用例
      demo        调试的代码,类似草稿,测试的代码,案例,包,代码被其他代码调用最好建立一个包
        course 包
        login 包
        other 包
      lib          一些公共的代码封装成函数和类供其他模块调用,
      report         导出来的测试报告,文件夹
      testcase        测试用例的包
        course        存放课程管理测试用例包
        teacher        存放教师管理测试用例包
      config.py      存储全局配置变量,比如邮箱配置,数据库的主机地址,用户名和登录密码,测试系统的ip和登录用户名密码等全局变量(便于后期维护)
      deReport.py    执行测试用例,生成测试报告用,执行一个py文件就能把测试用例都执行一遍,然后结果放在report里面取

  2:接口文档评审,根据接口文档

    每一个接口写一个正向的测试用例python代码去请求尝试一下是不是正常请求成功的,如果这都不能通过,测试没什么必要

    编写接口请求demo(),目的是间接评审接口文档。

  3:定义全局变量 config.py
    定义全局变量
    定义全局变量的目的方便维护。
    HOST='http://localhost'

  4:定义函数 写lib公共封装的测试代码
    实现课程管理模块:courseLib.py
      定义新增课程[add]、列出课程[list]、修改课程[modfy]、删除课程[delete]函数
      修改demo,添加函数调用
    实现用户登录模块:loginLIb.py
    定义login函数

  5:编写testcase测试用例

  正规都需要接口文档做接口测试,没有的话需要一个个请求抓包去尝试,

     自己写接口文档,接口文档一般开发来编写,前端也需要调用后端的接口,都需要接口文档
     java有自动生成接口的工具,swage

21:format-str的使用:

    用法一:
        name="abc"
        str_a=f'{{"name":"{name}","desc":"初中化学课程","display_idx":"4"}}'
        print(str_a)                    #输出:{"name":"abc","desc":"初中化学课程","display_idx":"4"}
        
        谨记:大括号需要加一层括号,不然报错,

    用法二:
        name="abc"
        print(f'{name}efg')             #输出:abcefg
        print(f'{{name}}efg')           #输出:{name}efg
        print(f'{{{name}}}efg')         #输出:{abc}efg

  自动化接口一般都是主流业务,登录接口一般不做自动化测试,

22:cookie

  chrome---F12--Application(应用)--cookies(查看每个站点的cookies,每个域名都有一条记录)

  Cookie可以翻译成小饼干、小甜品。
  为了解决HTTP协议的无状态问题。出现了Cookie和Session 的概念。
  1.cookie特点
    Cookie是分站点的,站点(比如百度站点,理解成域名或者ip地址,一台主机上的某个服务)和站点之间的Cookie是相互独立的
    浏览器的Cookies是保存在浏览器的某个位置的
    服务器端可以通过:响应头中的set-Cookie参数,对客户端的Cookie进行管理
    浏览器的每次请求,都会把该站点的所有的(不管有效还是没效)Cookie发送给服务器
    实现登录:Cookie + Session 配合使用的。

  Cookie 可以翻译为“小甜品,小饼干”
  Session 对象的使用就离不开 Cookie 的支持
  浏览器中的Cookie有个数限制的,大部分浏览器每个站点的Cookie 不能超过50个
  浏览器中的Cookie是大小限制的,大部分浏览器每个站点的Cookie 大小不能超过4Kb

23:session :

  session,服务器创建session对象,有一个sessionid属性(字符串),唯一且不重复,通过sessionid找到session,
  开发人员定义,sessionid多久不用就过期,服务器从数据库里面把sessionid删除,就无法使用了,开发人员一般规定30分支或者60分钟有效时间
  银行一般规定15分钟不操作就失效,session信息一般存在于服务器内存中或者redis等数据库中,(redis数据库中比较多,很早的时候存在内存中)
  服务器重启/redis数据库清空,session也没有了,开发人员提供注销session功能(一般退出这个功能,服务器把session删除了)

  用户登录成功,服务器就创建一个session对象
  服务器创建好session后通过响应头,把sessionid,传递给客户端
  客户端把sessionid放在浏览器的cookie中(f12可以看到) cookie简单来说是sessionid的一个家,
  下次请求,把sessionid通过请求头,携带给服务器(cookie里面的sessionid通过请求头携带给服务器)
  浏览器自己的机制:只要有这个域名的cookie,下次请求这个域名通过请求头加上cookie发送给服务器---浏览器里面的信息cookies
  服务器判断请求身份是否有效的sessionid(服务器内存里面是不是有效的)---如果存在就允许访问请求,访问链接,否则跳转到登录页面去
  这是一个校验的过程

  sessionid 如果浏览器关闭,服务器中的session照样存在,除非调用退出接口,服务器不知道你浏览器有没有退出,
    只能等session超时失效(一般60分钟,30分钟,15分钟,5分钟)越短越安全
    规定60分钟,但是过了30分钟又操作一下(客户端发送了请求才是操作),就会又恢复到60分钟才会session失效

  不是很安全,黑客盗用sessionid去使用

  策略:传递sessionid的时候就告诉浏览器,你关掉的时候把sessionid清空掉(告诉浏览器)
    浏览器就是这么设计的,session-会话(浏览器打开到关闭都是一个会话),浏览器关闭会话结束
   sessionid有两个属性:

    1:有效期 (服务器告诉浏览器这个sessionid有有效期期的,有效期=1970-1-1前一秒钟,代表sessionid是一个会话id,
    关了浏览器后自动死掉,下次打开没有sessionid了)
    服务器和浏览器的约定:因为服务器不知道浏览器什么时候关了会话,没有交互,关闭浏览器的话服务器中的session照样存在,
    约定sessionid的名称和值,还规定了有效期,设置有效期=1970-1-1前一秒钟
    这个有效期并不是服务器上session的有效期,这个时间和服务器没有任何关系,和浏览器的一个约定
    约定用了这样一个有效期(1970-1-1前一秒钟),这个时间不会变的,浏览器把sessionid的有效期信息放到cookie的失效日期字段中
    浏览器自己的行为,看到了过时的sessionid,就会在关闭浏览器的时候删除浏览器上缓存的sessionid
    并不是用的过程中删除,只会关闭浏览器才会删除
    有的浏览器expries显示是session(会话)有的浏览器expries显示是设置有效期=1970-1-1前一秒钟
    都是一个意思,约定关闭浏览器清空sessionid(会话)-----浏览器自己的行为
    关闭浏览器不能清空服务器上面的东西,但是能约定清空浏览器的session(会话)
    假如修改sessionid的expries(过期时间)为2020-xxx-xxx为当前时间后面的日期,

    限制关闭浏览器就不会删除这个cookie里面的sessionid了,只能尽量让这个http安全一点点而已,不是很完美
    代表关闭浏览器浏览器的sessionid就清空,


    sessionid 会话一般30分钟服务器就删除,主要还是看服务器的的session会话失效时间
    浏览器上的有效时间并没啥意义,只是过了有效期浏览器就删除这个cookie而已

    2:属性

      Set-Cookie: sessionid=tv03mm964llj3dw8kfapt8eurs4anp6t; HttpOnly; Path=/    登录响应头这个数据很重要,里面有sessionid值

      只有在登录的时候才能得到这个sessionid,拿到这个值后浏览器就会放到它的cookie里面去

      Path=/:

    通过响应头把sessionid递交给浏览器,

    登录之后就跳转到首页了,列出课程操作,也需要请求,点击登录按钮会连续发送两个请求:1:登录  2:列出课程get 

    列出课程的请求如下:
      GET http://localhost/api/mgr/sq_mgr/?action=list_course&pagenum=1&pagesize=20 HTTP/1.1
      Host: localhost
      Connection: keep-alive
      sec-ch-ua: "Google Chrome";v="87", " Not;A Brand";v="99", "Chromium";v="87"
      Accept: application/json, text/plain, */*
      sec-ch-ua-mobile: ?0
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36
      Sec-Fetch-Site: same-origin
      Sec-Fetch-Mode: cors
      Sec-Fetch-Dest: empty
      Referer: http://localhost/mgr/ps/mgr/index.html
      Accept-Encoding: gzip, deflate, br
      Accept-Language: zh-CN,zh;q=0.9
      Cookie: sessionid=tv03mm964llj3dw8kfapt8eurs4anp6t

    请求头里面有个Cookie: sessionid=tv03mm964llj3dw8kfapt8eurs4anp6t   这是浏览器自己在发送列出课程的http请求的时候自己带上的这个cookie

      cookie里面的信息全部带上,所有的cookie都会通过请求头的cookie发送给服务器,有多少发多少,

      所有的cookie都封装成一个字符串发送过去---cookie的操作

  退出浏览器服务器的session并不会消除,只有调用退出接口服务器才会注销session

24:Session可以翻译为会话

  1.session机制
    session 是一个对象,是服务器产生的,保存在服务器的内存中的
    session有自己的管理机制:包括session产生、销毁、超时等
    sessionID是session对象的一个属性,是全局唯一的,永远都不会重复的

25:教管系统登录做法

  当用户登录成功时,服务器产生一个session,同时将sessionID响应给客户端
  这个sessionID是通过响应头-->set-Cookie给客户端的
  浏览器如果拿到set-Cookie中的内容,就会立马保存起来,存在浏览器的cookie中(所以有人说在服务器端叫sessionID,在客户端就叫Cookie)
  之后的每次请求,都会把该站点的cookie传给服务器,当然也包括sessionID
  服务器端会根据客户端传送过来的sessionID进行有效性判断,判断通过则能访问,不通过则跳转到登录界面
  当用户发送退出登录请求时,服务器端会把session注销,同时sessionID置空响应给客户端
  浏览器如果拿到set-Cookie中的sessionID为空,则会删除sessionID

26:token (和session很像,类似一对兄弟,稍微优化了一下)

  Token可以翻译为令牌。(令牌就是一个字符串,没有什么对象,直接就是一个卡号(字符串),这个卡号经过一个加密的(加密后还是字符串))
  比如aaaaaa加密成askdaslkkdaslkdlask,通过响应头下发给浏览器的,一般不会存在cookie里面(不会缓存在浏览器的cookie里面)
  token也可以放在cookie里面,但是这种情况很少,经常放在请求头里面token:asdjkaskdja
  比如登录成功了给个token字符串,下次调用接口老老实实的把token放在请求头里面

  Token 也是由服务器产生的,存在服务器的内存或硬盘中
  Token 也有一套产生规则,会涉及到加密算法

27:用Token 来实现登录

  开发提供一个获取Token接口,根据用户名+密码,获取一个Token值
    返回一个Token (字符串)
  Token 值服务器通过什么给客户端的呢?
    通过响应头给客户端的。====次要
    通过响应消息体传给客户端。====主要
    通过Cookie传递给客户端 ====很少
    其他途径
  Token 是怎么传递给服务器的
    通过请求头 ====主要
    通过请求体单独的一个参数 ====次要
    其他途径

28:token和session基本上一样的原理:session是一个对象token是一个简单的加密的字符串

  响应头--下发给客户端
  请求头--发送给服务i去
  sessionid一般在cookie里面传到服务器(cookie也是请求头的一种)名字都是开发人员定义的
  token在请求头里面(也可以通过cookie携带,看开发定义)

  token和session原理是一样的,只不过位置不同而已,大同小异,

  token和session一套项目一般采用一种验证方式,不会采用两种

29:自动化用例编写:

  1:接口测试用例以接口文档为依据(以接口文档来写测试用例)

    功能测使用例以需求来写测试用例

  2:查看接口文档参数的说明和四大要素

    比如新增课程:
      课程名         name      String    是    汉字、字母、数字组成,长度1~20个字符
      描述       desc       String    否
      展示次序          Display_idx       String    是      整形数字,范围0-999  

    关注参数是否必填,传的是数字还是字符串,课程名称由什么组成的
    长度有没有什么要求,数字的范围---获取这些信息

  3:接口测试用例模板
    1:用例编号
    2:功能模块名称
    3:标题
    4:预期结果
    5:调用方法
    6:请求参数
    7:断言结果(预期结果 )
    8:执行结果 pass,fail
    9:错误原因

  4:写测试用例很多方法,
    必填,选填,等价类,边界值,错误推测法,...

  用例简单模板如下

用例编号      功能         标题                        预期结果               调用方法          请求参数                                                           断言            执行结果    错误原因    
10001:        增加课程    所有的字段都正常填写         正确,返回成功              add            {"name":"初中化学","desc":"初中化学课程","display_idx":"4"} {"retcode": 0}
10002:        增加课程    填写内容和上面一模一样       错误,课程名称已经存在        add            {"name":"初中化学","desc":"初中化学课程","display_idx":"4"} {"retcode": 2}    
10003:        增加课程    课程名称不填,其他正常填写    错误,缺少课程名称           add            {"name":"","desc":"初中化学课程","display_idx":"4"} {"retcode": 2}    
10004:        增加课程    不填展示次序,其他正常填写    错误,缺少展示次序           add            {"name":"初中化学2","desc":"初中化学课程","display_idx":""} {"retcode": 2}        
    上面是必填项的测试用例

    边界值
    课程名称长度1~20个字符的边界值:
        20的边界值:20,21            (20ok的话19肯定没问题,20边界值20和21就行)
        
        一定填写20个与可以填写20个以内是有区别的
        
        离点21    上点20    内点:(最好范围中间的值)10
        
10005:        增加课程    课程名称填写20个汉字。其他正常填写     正确,返回成功        add    
10006:        增加课程    课程名称填写21个汉字。其他正常填写     错误,课程名称太长     add    
10007:        增加课程    课程名称填写10个汉字。其他正常填写     正确,返回成功         add            
10008:        增加课程    课程名称填写1个汉字。其他正常填写      正确,返回成功         add    
10009:        增加课程    课程名称填写没有汉字。其他正常填写      错误,课程名称太长     add            和上面的10003重合,不写,测试用例的优化
        
    展示次序    Display_idx    String    是        整形数字,范围0-999
        也需要搞个边界值测试

10010:        增加课程    展示次序填写999,其他的正常填写        正确,返回成功              add    
10011:        增加课程    展示次序填写455,其他的正常填写        正确,返回成功              add    
10012:        增加课程    展示次序填写1000,其他的正常填写       错误,返回展示次序太大       add            
10013:        增加课程    展示次序填写0,其他的正常填写          正确,返回成功              add            
10014:        增加课程    展示次序填写-1,其他的正常填写         错误,返回展示次序不能小于0    add    

    课程名称汉字、字母、数字组成,
    正向测试用例:正向用例:汉字+字母+数字组合填写
    逆向测试用例:课程名称包含特殊字符
10015:        增加课程    课程名称包含特殊字符$,其他的正常填写            错误,课程名称包含特殊字符(能写一堆特殊字符)                            add        
                                                                        也不是所有的特殊字符都要填写,挑选几个组合一下,空格句号逗号都算
    展示次序,整形数字
    正向测试用例:正向填写0-999的数字
    逆向测试用例:小数:0.1,复数:-1 字符串:%¥%
10016:        增加课程    展示次序填写0.1,其他的正常填写                错误,返回展示次序不合法                add            
10017:        增加课程    展示次序填写0.22,其他的正常填写            错误,返回展示次序不合法                add        
10018:        增加课程    展示次序填写%¥%,其他的正常填写            错误,返回展示次序不合法                add            

    还可以考虑课程名称特殊字符+数字+什么    
10019:        增加课程    课程名称a            正确,返回成功                add            
10020:        增加课程    课程名称1            正确,返回成功                add            


10020:        删除课程    删除课程id为空                    错误,id不能为空        add    
10022:        删除课程    正常删除                          正确,返回成功          add    
10022:        删除课程    删除id(字母,小数,特殊字符等等)   正确,返回成功           add    


10022:        列出课程    正常删除                正确,返回成功                add    

30:初级自动化代码编写步骤(不使用框架)

  1:demo==接口文档的评审(接口都能走通)
  2:编写接口测试用例(excel格式的)
  3:执行测试用例(执行excel表格的用例并且执行结果写到excel里面--不是最终的结果)
    复杂的测试用例:新增-然后列出课程(excel里面写不了的,unitest管理excel测试用例以及管理我们的复杂的py文件写的测试用例)
    一:定义一个函数读取excel表的测试用例,返回列表
    二:写一个循环列表,发送请求--返回结果 (调用了之前封装的add,delete等函数)
    三:执行的返回结果写到excel里面去

31:课程名称随机数 

  10001:   增加课程    所有的字段都正常填写   正确,返回成功 add     {"name":"初中化学{coursename}","desc":"初中化学课程","display_idx":"4"} 

    {"name":"初中化学{{coursename}}","desc":"初中化学课程","display_idx":"4"}   excel里面使用{{coursename}}变量,让用例变化不写死,

    demo_random.py                    练习文件,
        #python实现随机数的方式:1:random   2:时间戳time

        #1:random       有极小的概率重合
        # import random
        # print(random.random())              #0-1的小数
        # print(random.randint(1000,99999))    #int返回一个整数
        #
        # 2:time
        import time
        # print(time.time())                  #1610655885.5234265     从1970-1-1-000:00计算到目前为止所经过的微秒数
        # print(str(int(time.time()*1000)))

        randonStr=str(int(time.time()*1000))
        print(randonStr)
        courseName='大学英语{{name}}'
        print(courseName.replace('{{name}}',randonStr))

  2:需要编写一个函数读取excel测试用例
    1:workbook=工作簿=excel
    2:worksheet=工作表=sheet
    3:行和列的概念(从0开始计算的(2,1)表示3行2列)

    excel方式执行教官系统自动化并且把执行结果写到excel里面 完整代码:

32: UnitTest概念与用法+ddt 

  UnitTest是python自带的单元测试框架,不需要pip安装, 主要适用于单元测试,可以对多个测试用例进行管理和封装
  UnitTest 也叫 PyUnit,它提供了很多类和方法来处理各种测试工作

33:单元测试:

  比如一个项目,项目里面实现了一些功能,另外一部分的代码去测试另外一部分的代码(单元测试)
  test模块测试功能模块--单元测试 (本质一部分代码测试另外一部分代码)

  UnitTest有很多特点:可以做自动化

34:UnitTest待解决的问题:(当前我们还需要解决的问题)

  1:如何批量执行测试用例 UnitTest循环读取excel里面的用例for循环执行很多测试用例,一个一旦异常后面的测试用例都不会执行了--需要用到ddt数据驱动
  2:如何导出html格式的报告 HTMLTestRunner 使用这个库

35:测试三思:(测试框架的作用)

  1:测试数据如何自动化清除(运行前后)
  2:多个测试用例,如何组织一起执行(很多测试用例,如何一键运行所有的测试用例或者选择性运行测试用例)
  3:多个测试用例的结果,如何展示在一份报告上

  测试框架就能解决上面的问题
  UnitTest语法很简单

36:unittest主要由四大功能组成

  1:测试用例  testcase
    测试用例必须在类中              (测试用例放在class类里面,类必须继承TestCase)
    测试用例所在的类,必须继承unittest.TestCase
    所有的测试用例的方法名称必须以test开头(test001,test002)
    测试用例的执行顺序,按照Ascill码顺序排序(0~9,A~Z,a~z)---按照名称运行的(一个个字母开始比较的)
    利用父类TestCase的assertXXXX方法来断言     16种断言方式,父类提供的
    断言失败后面的代码不会运行,但是不影响其他testcase的运行,也不影响setup和teardown的运行,测试用例和测试用例之间相互独立
    用@unittest.skip(reason)装饰来跳过测试用例      reason可以填写不想执行的原因

    assert断言后面加python表达式,UnitTest只有使用了断言,测试结果才会体现在测试报告上,
    UnitTest不能直接使用assert,assert是python语言里面的,需要使用UnitTest自己的独有的assert断言,如下
      方法                 检查
      assertEqual(a, b)             a ==b
      assertNotEqual(a, b)             a !=b
      assertTrue(x)               bool(x) is True
      assertFalse(x)             Bool(x) is False
      assertIs(a, b)               a is b
      assertIsNot(a, b)               a is not b
      assertIsNone(x)             x is None
      assertIsNotNone(x)           x is not None
      assertIn(a, b)                a in b
      assertNotIn(a, b)                 a not in b
      assertIsInstance(a, b)             isinstance(a,b)
      assertNotIsInstance(a, b)            not isinstance(a,b)    

 

      isinstance() 函数来判断一个对象是否是一个已知的类型,

      isinstance() 会认为子类是一种父类类型,考虑继承关系

      type() 不会认为子类是一种父类类型,不考虑继承关系。

      isinstance(object, classinfo)

        object -- 实例对象。

        classinfo -- 可以是直接或间接类名、基本类型或者由它们组成的元组。

        isinstance (a,int)

        isinstance (a,(str,int,list))   # 是元组中的一个返回 True

 

    断言的简单示例

      比如: self.assertIn(a,b)
      self.assertEqual(a,b) 这些assert都是UnitTest自己提供的,
      self.assertEqual(1,1) 断言1等于1
        使用UnitTest自己的断言规则,只有用到这套规则才会把所有的测试结果都打印到报告上去
        报告上显示一些文字可以加:self.assertEqual(1,2,'XXX原因测试不通过')
'        XXX原因测试不通过'可以从请求的响应拿出来放进去显示在html报告上

  2:测试固件  testfixture (数据初始化和清除,方法级别和类级别的)
    也叫测试夹件,主要工作是【初始化和善后】
    测试固件分为两种,一种是类级别的,一种是方法级别的
    类级别的测试固件,所有测试用例执行之前与之后运行一次
    方法级别的测试固件,每个测试用例执行之前与之后都运行一次

    运行之前:希望所有的运行数据都没有,课程模块无数据,并且处于登录状态
    运行之后:希望所有的运行数据都没有,课程模块无数据

  3:测试套件  testsuite (解决多个测试用例组织在一起执行)
    是用来组织测试用例的
    一个a.py文件一个测试用例
    一个b.py文件也可以放多个测试用例
    测试套件:a和b都放进去,还有执行次序,先放进去先执行

    在项目根目录下写一个doReport.py,进后只要运行doReport.py文件就能把想要运行的测试用例(套件)都运行
    doReport.py调用testcase里面的测试用例运行

    方法一:用例逐个添加到suite

      suite = unittest.TestSuite()          #suite名字自己随便起,类的实例化产生一个suite测试套件,套件里面添加测试用例
      suite.addTest(MyTest200("test_203"))     #MyTest200("test_203")    类的名字(类里面的方法)    ,使用MyTest200这个类还需要导入
      suite.addTest(MyTest200("test_201"))    

      skip跳过的放到这个套件不会执行

      方法一:很麻烦,一个测试用例需要添加一次

    方法二:用例放入列表中,再添加到suite

      suite = unittest.TestSuite()
      list=[类名1("方法名1"),类名2("方法名2")]
      suite.addTests(list)

    方法三:通过TestLoader类的discover 方法来

    unittest.defaultTestLoader.discover('用例所在的模块', pattern="*通配*.py")    #匹配.py文件
    (一和二是以方法名字来加到套件执行,添加次数太多,三以.py文件(或者模块的名称)执行)
    

    unittest.defaultTestLoader.discover('demo.other', pattern="demo_u*.py")   匹配demo/other文件里面所有的以demo_u开头的py文件里面的测试用例


    discover(start_dir,pattern='test*.py',top_level_dir=None)
      -case_dir:这个是待执行用例的目录。
      -pattern:这个是匹配脚本名称的规则,test*.py 意思是匹配 test 开头的所有脚本。
      -top_level_dir:这个是顶层目录的名称,一般默认等于 None 就行了。

  4:测试运行器  

    testrunner 解决测试报告的(unittest自带的是文本格式的报告(控制台显示),希望导出html格式需要引入插件(htmltextrunner))
    是用来执行测试用例的
    runner=unittest.TextTestRunner()       #实例化TestRunner类
    runner.run(suite)              #执行用例

37:unittest--demo代码演示   

2021-1-14APIAuto
    demo
        other
            demo_unittest.py                
                import unittest
                class  DemoUnittest(unittest.TestCase):                                            #testcase类必须继承unittest.TestCase类
                    def setUp(self):
                        print('方法级别的初始化,类里面的每个方法都会执行一次初始化>>>>')

                    def tearDown(self):
                        print('方法级别的清除,类里面的每个方法都会执行一次清除>>>>')

                    def test001(self):
                        print('执行testcase001')

                    def test002(self):
                        print('执行testcase002')

                    def test003(self):
                        print('执行testcase003')
                    def test004(self):
                        print('执行testcase004')
            
            demo_unittest2.py
                import unittest
                class  DemoUnittest2(unittest.TestCase):
                    @classmethod
                    def tearDownClass(cls):
                        print('>>>>>类级别的环境清除运行了')

                    @classmethod
                    def setUpClass(cls):
                        print('>>>>>类级别的数据初始化运行了\n\r')

                    def test001(self):
                        print('执行testcase001')

                    def test002(self):
                        print('执行testcase002')

                    @unittest.skip('老子不想执行了')
                    def test003(self):
                        print('执行testcase003')

                    def test004(self):
                        print('执行testcase004')

        doReport.py                        #demo文件在项目根目录执行所有的测试用例
            import unittest
            from demo.other.demo_unittest import DemoUnittest
            from demo.other.demo_unittest2 import DemoUnittest2
            from ClassicHTMLTestRunner import HTMLTestRunner            #生成html使用这个类


            #执行方法一:把每个类的每个测试用例添加到suite 测试套件            很蠢的方法,需要一个个添加
                suite = unittest.TestSuite()
                suite.addTest(DemoUnittest("test001"))
                suite.addTest(DemoUnittest("test002"))
                suite.addTest(DemoUnittest("test003"))
                suite.addTest(DemoUnittest("test004"))
                suite.addTest(DemoUnittest2("test001"))
                suite.addTest(DemoUnittest2("test002"))
                suite.addTest(DemoUnittest2("test003"))
                suite.addTest(DemoUnittest2("test004"))
                runner=unittest.TextTestRunner()        #实例化TestRunner类
                runner.run(suite)                       #执行用例        


            #执行方法二:把每个类的每个测试用例添加到list,然后suite.addTests(list)
                suite = unittest.TestSuite()
                list=[DemoUnittest('test001'),DemoUnittest('test002'),DemoUnittest('test003'),DemoUnittest('test004'),
                      DemoUnittest2('test001'),DemoUnittest2('test002'),DemoUnittest2('test003'),DemoUnittest2('test004')]
                suite.addTests(list)
                runner=unittest.TextTestRunner()        #实例化TestRunner类
                runner.run(suite)                       #执行用例


            #执行方法三            #匹配py文件运行py文件里面的测试用例
                suite=unittest.defaultTestLoader.discover('demo.other', pattern="demo_u*.py")
                runner=unittest.TextTestRunner()
                runner.run(suite)
        
        
            #执行方式四        #使用HTMLTestRunner执行测试用例,导出html格式的测试报告
                import unittest
                from ClassicHTMLTestRunner import HTMLTestRunner            #使用HTMLTestRunner插件运行报告输出为html定向到repory文件夹

                suite=unittest.defaultTestLoader.discover('demo.other', pattern="demo_u*.py")
                # runner=unittest.TextTestRunner()            #使用TextTestRunner()运行报告输出到控制
                                                              #使用HTMLTestRunner插件运行报告输出为html定向到repory文件夹
                reportFile=open('.//report//ywt.html','wb')
                runner=HTMLTestRunner(tester='袁文韬',title='测试报告',
                                               description='自动化接口测试报告',
                                               stream=reportFile,
                                               verbosity=2)
                # tester测试报告的执行者
                # title='测试报告'   测试报告的名称
                # description    描述
                # stream 文件
                # reportFile  报告的文件路径
                # verbosity  报告的级别,报告详细的程度
                runner.run(suite)           # 执行用例

38:不使用unittest框架直接读取excel里面的用例然后for循环执行完成后结果街道excel表里面的演示代码:如下

2021-1-14APIAuto                            #项目根目录 

    lib
        loginLib.py                                        #接口的登录函数,登录后得到cookie
            import requests
            from config import HOST
            def login(Flage=False):
                url = f'{HOST}/api/mgr/loginReq'
                headers = {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
                payload = {"username": "auto", "password": "sdfsdfsdf"}
                reps = requests.post(url, headers=headers, data=payload)
                if Flage==False:
                    return reps.cookies
                else:
                    return reps.json()

            if __name__ == '__main__':
                print(login())     
courseLib.py     #函数库,写好各种接口请求,拿到返回数据 import requests
from config import HOST from lib.loginLib import login cookies=login() def add(name,desc,display_idx): '''新增课程''' url = f'{HOST}/api/mgr/sq_mgr/' headers = { 'Content-Type': 'application/x-www-form-urlencoded' } payload = { 'action': 'add_course', 'data':f'{{"name":"{name}","desc":"{desc}","display_idx":"{display_idx}"}}' } try: r = requests.post(url, cookies=cookies, headers=headers, data=payload) return r.json() except: return {'retcode': 999, 'reason': '异常情况'} def delete(id): '''删除课程''' headers={ 'Content-Type':'application/x-www-form-urlencoded' } url=f'{HOST}/api/mgr/sq_mgr/' payload={ 'action':'delete_course', 'id':id } #payload='action=delete_course&id= 5125' 这样传递参数也可以的 try: r=requests.delete(url,headers=headers,cookies=cookies,data=payload) return r.json() except: return {'retcode': 999, 'reason': '程序异常'} def modify(id,name,desc,display_idx): '''修改课程''' url = f'{HOST}/api/mgr/sq_mgr/' headers = { 'Content-Type': 'application/x-www-form-urlencoded' } payload = { 'action':'modify_course', 'id':id, 'newdata':f'{"name":"{name}","desc":"{desc}","display_idx":"{display_idx}"}' } try: reps = requests.put(url, headers=headers,cookies=login(),data=payload) return reps.json() except: return {'retcode': 999, 'reason': '程序异常'} def list_lesson(pagenum,pagesize): '''列出课程''' url = f'{HOST}/api/mgr/sq_mgr/?action=list_course&pagenum={pagenum}&pagesize={pagesize}' try: r = requests.get(url, cookies=login()) r.encoding = "unicode_escape" return r.json() except: return {'retcode':999,'reason':'程序异常'} def add2(): #新增课程方式二 url = f'{HOST}/apijson/mgr/sq_mgr/' headers = { 'Content-Type': 'application/json' } payload = '''{ "action" : "add_course_json", "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"} }''' payload = payload.encode('utf-8') # 传的中文需要编码一下 try: r = requests.post(url, cookies=login(), headers=headers, data=payload) r.encoding = "unicode_escape" return r.json() except: return {'retcode': 999, 'reason': '程序异常'} if __name__ == '__main__': print(list(1,2)) excelManage.py #封装execl表的读写方法 #excel管理目录 读+写 #1:读取excel中的数据 #功能:传一个excel的位置和第几个工作表,返回数据列表(所有的测试用例) #1:打开execl,获取workbook工作簿对象 #2:从工作簿中,获取worksheet工作表对象 #3:对worksheet工作表进行循环追行读取数据放在列表中 #return 返回数据列表 import xlrd #xlrd excel写入的库,让操作excel读取更简单 def read_Excel(excelDir,sheet_index=0): #sheet_index=1 默认读取第一个sheet表里面的数据 workBook=xlrd.open_workbook(excelDir) #打开excel创建一个excel对象 worksheet = workBook.sheet_by_index(sheet_index) #从工作簿得到工作表(根据第几个工作表下标) nrows=worksheet.nrows #获取表中有多少行的数据 46行 #得到里面的内容,循环读取,for循环 reslist=[] for i in range(1,nrows): # parameter=worksheet.cell(i,5).value #获取一个格子的数据,返回一个字符串 #worksheet.row_values() #读取一行的数据,返回一个列表 rowdata=worksheet.row_values(i) reslist.append(rowdata) return reslist #返回列表嵌套列表 from xlutils.copy import copy #xlutils这个库用来复制excel ,xlwt也可以,这个copy更好用,复制过来格式还在的 #2:复制excel表格 def getNewExcel(excelDir): #1:打开excel,得到工作簿 workBook=xlrd.open_workbook(excelDir,formatting_info=True) #打开才能复制 #formatting_info=True 加这个参数复制的时候就会有样式了 ,以格式的方式打开excel,打开就有格式了,复制也会有格式的 #2:复制一个全新的工作簿 newWorkBook = copy(workBook) #老的复制一个新的 return newWorkBook #新的工作簿返回 if __name__ == '__main__': # print(read_Excel('../data/教管系统-测试用例V1.2.xls')) newWorkBook=getNewExcel('../data/教管系统-测试用例V1.2.xls') newWorksheet = newWorkBook.get_sheet(0) #get_sheet传下标0或者表名称'课程管理'都可以打开一个表任务 print(newWorkBook) sendCourseRequest.py #承上启下,写一个函数,接收excel表里面一列的数据,返回接口请求结果(excel里写了add,delete,list三种接口,做个判断) from lib.excelManage import read_Excel import json import time from lib.loginLib import login from lib.courseLib import add,list_lesson,delete cookie=login() #功能:传一个列表数据行(列表格式),返回请求结果(字典格式 ) def SendCourseRequest(row): #row是每一行的数据 randonStr = str(int(time.time() * 1000)) if row[4]=='add': data=json.loads(row[5]) #读取的字符串类型转化成字典 coursename=data['name'].replace('{{courseName}}',randonStr) #名称变量替换 resp=add(coursename,data['desc'],data['display_idx']) #返回json响应内容 return resp #返回执行结果 elif row[4]=='delete': id=json.loads(row[5])['id'] resp=delete(id) return resp #返回指向结果 elif row[4]=='list': data=json.loads(row[5]) resp=list_lesson(data['pagenum'],data['pagesize']) return resp if __name__ == '__main__': list=read_Excel('../data/教管系统-测试用例V1.2.xls') for i in range(len(list)): resp=SendCourseRequest(list[i]) time.sleep(0.01) print(resp) doEexcel.py #main程序主入口,调用编写好的各种函数,得到接口返回结果,写到excel表里 from lib.excelManage import read_Excel,getNewExcel from lib.sendCourseRequest import SendCourseRequest import time,json #1:定义一个excel存放路径 excelDir='../data/教管系统-测试用例V1.2.xls' #测试用例excel表的路径 saveDir='../report/测试结果2020-1-18.xls' #执行结果excel表存放的路径 #2:读取测试用例,返回一个列表嵌套列表(嵌套的列表是excel表里面每一行的数据) info_list = read_Excel(excelDir) #读取excel里面每一行的数据,返回一个[[],[],[]]数据 #3:返回的响应消息体结果写回到excel,把测试用例的excel复制一份再写 #1复制一个全新的excel #2打开新的excel得到一个工作表 #3:在7-8列些内容 #4:新的excel表写完后需要保存excel到report报告里面 workBookNew=getNewExcel(excelDir) #新的工作簿 sheetbookNew=workBookNew.get_sheet('课程管理') #得到新的工作表 #4:执行测试用例,for循环一行行执行,边执行边在excel里面写结果,写完后再保存新的excel for i in range(len(info_list)): row=info_list[i] resp=SendCourseRequest(row) time.sleep(0.01) if resp['retcode']==json.loads(row[6])['code']: #如果实际等于预期 sheetbookNew.write(i + 1, 7, 'pass') else: sheetbookNew.write(i + 1, 7, 'fail') if 'reason' in resp.keys(): #返回值有reason就写,没有就不写 sheetbookNew.write(i + 1, 8,resp['reason'] ) #5:新的excel写完了测试结果保存到report文件夹 workBookNew.save(saveDir) config.py #配置文件 HOST='http://localhost:80'

39:unittest+DDT执行接口测试得到返回结果的代码演示:

2021-1-14APIAuto
    demo
        course
            demo_ddt.py                                                #ddt执行测试用例的源代码
                import unittest,time,json                
                from ddt import ddt,data
                from lib.demo_calculator import Calculator                #Calculator一个加减乘除的类,在lib里面,如下
                mydata=[[1,2,3],[3,4,7],[4,5,9]]                        #进行ddt的数据
                from lib.excelManage import read_Excel                    #和上面的代码类似,读取excel里面的数据的函数
                from lib.sendCourseRequest import SendCourseRequest        #和上面的代码类似,SendCourseRequest函数

                excelDir='../../data/教管系统-测试用例V1.2.xls'

                mydata2=read_Excel(excelDir)

                @ddt                                            #为了做数据驱动使用ddt
                class UnittestDemo(unittest.TestCase):
                    @classmethod
                    def setUpClass(cls):
                        cls.calculator=Calculator(9,3)
                        print('类级别的setup执行>>>>>>\n')

                    def test_add(self):                 #测试加法
                        print('执行测试用例add')
                        result=self.calculator.add()
                        self.assertEqual(result,12)

                    def test_sub(self):                 #测试减法
                        print('执行测试用例sub')
                        result=self.calculator.sub()
                        self.assertEqual(result,6)

                    def test_mul(self):                 #测试乘法
                        print('执行测试用例mul')
                        result=self.calculator.mul()
                        self.assertEqual(result,27)

                    def test_div(self):             #测试除法
                        print('执行测试用例div')
                        result=self.calculator.div()
                        self.assertEqual(result,3.0)


                    # def test_div2(self):             #测试加法2
                    #     for i in range(len(list)):
                    #         list=[[1,2,3],[3,4,7],[4,5,9]]
                    #         for i in range(len(list))
                    #             result=self.calculator.add2(list[i][0],list[i][1])
                    #             self.assertEqual(result,list[i][2])
                    #             print('执行测试用例div2')
                    #     #这样for循环断言,只要里面出现一次断言失败,后面的的循环就不会再跑了--为了多用例执行使用DDT


                    @data(*mydata)                       #*mydata解包,列表分解成一个个参数
                    def test_div2(self,data):             #data相当于@data里面的参数的一项
                        result=self.calculator.add2(data[0],data[1])
                        self.assertEqual(result,data[2])
                        print('执行测试用例div2')
                        #for循环断言,只要里面出现一次断言失败,后面的的循环就不会再跑了--为了多用例执行使用DDT
                            #1:pip install ddt
                            #2:from ddt import ddt,data
                            #3:testcase类前面加@ddt装饰器
                            #4:类里面用到ddt的方法加@dara装饰器
                            #5:@data可以放我们的参数(或者参数的名称)

                    @data(*mydata2)         #@data中每个值会传成单个参数到方法中(参数集合)
                    def test_007(self,row):
                        resp = SendCourseRequest(row)
                        time.sleep(0.01)
                        if 'reason' in resp.keys():
                            self.assertEqual(resp['retcode'],json.loads(row[6])['code'],resp['reason'])
                        else:
                            self.assertEqual(resp['retcode'], json.loads(row[6])['code'])
            
    lib
        demo_calculator.py                    #简单的加减乘除函数
            class Calculator():
            def __init__(self, a, b):
                self.a = int(a)
                self.b = int(b)

            def add(self):
                return self.a + self.b

            def sub(self):
                return self.a - self.b

            def mul(self):
                return self.a * self.b

            def div(self):
                return self.a / self.b

            def add2(self, b, c):
                return b + c    
    
    doReport.py                                    #在这里定义执行testcase并且生成html报告
        import unittest
        from ClassicHTMLTestRunner import HTMLTestRunner            #使用HTMLTestRunner插件运行报告输出为html定向到repory文件夹
        from demo.course.demo_ddt import UnittestDemo


        suite=unittest.defaultTestLoader.discover('demo.course', pattern="demo_ddt.py")
        # runner=unittest.TextTestRunner()            #使用TextTestRunner()运行报告输出到控制
                                                      #使用HTMLTestRunner插件运行报告输出为html定向到repory文件夹
        reportFile=open('.//report//ywt.html','wb')
        runner=HTMLTestRunner(tester='袁文韬',title='测试报告',
                                       description='自动化接口测试报告',
                                        stream=reportFile,
                                       verbosity=2)
        # tester测试报告的执行者
        # title='测试报告'   测试报告的名称
        # description    描述
        # stream 文件
        # reportFile  报告的文件路径
        # verbosity  报告的级别,报告详细的程度
        runner.run(suite)           # 执行用例

40:代码再优化,环境的清除+结合登录cookie来实现代码

    courseLib.py                                    #改写了请求方法,每个请求函数需要一个sessionid的参数,还写了个delete_All_Lesson的初始化函数
        import requests
        from config import HOST
        from lib.loginLib import login

        def add(name,desc,display_idx,sessionid):
            '''新增课程'''
            url = f'{HOST}/api/mgr/sq_mgr/'
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Cookie':f'sessionid={sessionid}'                                            #这么组装cookie也可以
            }
            # cookies={'sessionid':sessionid}                                               这么写组装一个cookies也可以
            payload = {
                'action': 'add_course',
                'data':f'{{"name":"{name}","desc":"{desc}","display_idx":"{display_idx}"}}'
            }
            try:
                r = requests.post(url, headers=headers, data=payload)
                return r.json()
            except:
                return {'retcode': 999, 'reason': '异常情况'}

        def delete(id,sessionid):
            '''删除课程'''
            headers={
                'Content-Type':'application/x-www-form-urlencoded',
                'Cookie': f'sessionid={sessionid}'
            }
            url=f'{HOST}/api/mgr/sq_mgr/'
            payload={
                'action':'delete_course',
                'id':id
            }
            #payload='action=delete_course&id= 5125'      这样传递参数也可以的
            try:
                r=requests.delete(url,headers=headers,data=payload)
                return r.json()
            except:
                return {'retcode': 999, 'reason': '程序异常'}

        def delete_All_Lesson():
            '''删除课程所有课程做环境初始化和清除'''
            sessionid=login()
            all_list=list_lesson(1,999,sessionid)['retlist']
            for i in all_list:
                delete(i['id'],sessionid)



        def modify(id,name,desc,display_idx,sessionid):
            '''修改课程'''
            url = f'{HOST}/api/mgr/sq_mgr/'
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
                'Cookie': f'sessionid={sessionid}'
            }
            payload = {
                'action':'modify_course',
                'id':id,
                'newdata':f'{"name":"{name}","desc":"{desc}","display_idx":"{display_idx}"}'
            }
            try:
                reps = requests.put(url, headers=headers,data=payload)
                return reps.json()
            except:
                return {'retcode': 999, 'reason': '程序异常'}

        def list_lesson(pagenum,pagesize,sessionid):
            '''列出课程'''
            url = f'{HOST}/api/mgr/sq_mgr/?action=list_course&pagenum={pagenum}&pagesize={pagesize}'
            headers = {
                'Cookie': f'sessionid={sessionid}'
            }
            try:
                r = requests.get(url,headers=headers)
                r.encoding = "unicode_escape"
                return r.json()
            except:
                return {'retcode':999,'reason':'程序异常'}

        def add2(sessionid):
            url = f'{HOST}/apijson/mgr/sq_mgr/'
            headers = {
                'Content-Type': 'application/json',
                'Cookie': f'sessionid={sessionid}'
            }
            payload = '''{
              "action" : "add_course_json",
              "data": {"name":"初中化学","desc":"初中化学课程","display_idx":"4"}
            }'''
            payload = payload.encode('utf-8')  # 传的中文需要编码一下
            try:
                r = requests.post(url,headers=headers, data=payload)
                r.encoding = "unicode_escape"
                return r.json()
            except:
                return {'retcode': 999, 'reason': '程序异常'}


        if __name__ == '__main__':
            sessionid=login()
            print(list_lesson(1,2,sessionid))
            delete_All_Lesson()    
            
            

    demo_unittest.py
        import unittest
        import time
        from lib.courseLib import add,delete,delete_All_Lesson,modify,list_lesson
        from lib.loginLib import login

        class  DemoUnittest(unittest.TestCase):
            # @classmethod
            # def tearDownClass(cls):
            #     delete_All_Lesson()

            @classmethod
            def setUpClass(cls):                    #初始化函数:1:清除所有的课程  2:登录把sessionid取到
                delete_All_Lesson()
                cls.sessionid = login()             #cls    变成类属性,类变量,类的静态变量,实例方法可以使用类变量

            @classmethod
            def clearDb(cls):
                pass;

            def test001(self):
                pass
                '''
                path = r'E:\ProjectCodeForPy\APIAutoTest20200422\data\教管系统-测试用例V1.2.xls'
                # 1-1 从Excel中读取工作表中的测试用例
                retList = readExcel(path, 0)
                # print(retList)
                # 1-2 循环发送请求
                for i in range(0, len(retList)):
                    time.sleep(0.0001)
                    row = retList[i]
                    # print(row)
                    ret = sendCourseReq(row)
                    # print(ret)
                    colus7 = json.loads(row[6])  # 第7列的值
                    # print(colus7)
                    # 1-3 断言
                    if ret['retcode'] == colus7['code']:
                        print(row[0] + '测试通过')
                    else:
                        print(row[0] + '测试不通过')
                '''


            def test003(self):
                courseName = "大学英语" + str(int(time.time() * 10000))
                # 1-1 先列出课程
                retList0 = list_lesson(1,400,self.sessionid)                #self.sessionid调用类变量sessionid
                # 1-2 再新增课程
                ret = add(courseName, '课程描述', '0',self.sessionid)
                print(ret)
                #1-3 断言1
                self.assertEqual(ret['retcode'] ,0,'新增课程失败1')
                print(">>>>>新增课程测试通过1")
                # 1-4 再次列出课程
                retList1 = list_lesson(1, 400,self.sessionid)
                #1-5 断言2
                self.assertEqual(retList0['total'] + 1,retList1['total'],'新增课程失败2')
                print(">>>>>新增课程测试通过3")



            @unittest.skip('skip的原因')
            def test002(self):
                print('方法二调用了')

41:https请求实例(https带证书的)

  requests里面加个参数就可以了:verify=False
  发送一个https请求,https请求需要证书的,加verify=False参数就不需要证书,就能发出去请求了
  https是单向认证的话,加verify=False参数可以发送请求
  如果是双向的认证话还是需要证书的,再加个证书参数,ssh='证书路径',requests请求加个参数(证书开发提供的,指定证书位置)

42:token请求案例:

  和cookie类似,很多项目都使用的token:
  token和sessionid差不多  看项目需求,架构师规定的
  token值通过接口请求获得
  token :

    1:请求头里直接携带token(这个最保险,请求头禁用不了)
    2:请求头里面的cookie携带token(cookie不稳,浏览器可以设置禁用cookie,cookie大小有限制)
    3:请求体(正文)携带token

  token加密:(MD5,base64)
  token不能放敏感信息,因为能够被解密的

43:数据加密:

  加密算法主要分为:哈希算法、对称加密算法、非对称加密算法。
  哈希算法:如:MD5/SHA256      (加密后无法解密的算法就算哈希算法,不可逆的--哈希算法很多,md5是代表)
  对称加密算法:如:DES/AES/CBC   (一个密码加密后,用同样的密钥可以解密(一把钥匙))加密解密速度快,
  非对称加密算法:如:RSA (加密速度比较慢 )
    两把密钥:一把用于加密,一把用于解密,
    加密密钥:公钥
    解密密钥:私钥

44:简单的md5能够解密:

  因为做了一个md5的超级数据库,把加密结果保存到数据库,然后反向查询

  但是复制的md5加密解密不了的

45:base64(图片比较多,图片放到json字符串,报文里面传输)和urlencode编码这两种都不是算法,只是一种编码,和加密解密算法不同,不涉及到加密

46:加密算法使用场景:

  1:用户登录(一般使用md5加密和非对称加密来加密登录密码)
  2:数据一致性
    比如课程新增接口,传递的参数是action+data,数据包的参数很可能被别人篡改掉
    别人改了请求体的参数,比如金额相关的字段,这样明文传输抓包后修改传输不安全
    数据进行加密,
    action=add_course&num=1&adb=加密字符(action=add_course&num=1这一整段的md5进行加密)
    后台拿到这个参数,把action=add_course&num=1这个参数后台再去md5加密一下,再去和加密字符进行比较,正确才校验成功--安全级比较高
    接口测试碰到场景很多,自己进行接口加密
  3:数据库里面的密码:账户密码,以什么算法加密(自定义加密算法或者md5双重加密)

47:内网:互联网访问不到的,有个图片,项目,提供接口

  -网上办事大厅展示图片,调用获取图片接口(放封图片地址不行,只能以字符形式传输图片)

48:哈希算法加密后不可解密,怎么判断密码的正确性:

  用户a:淘宝网注册一个账户:密码:123456,淘宝网后台数据库存储的是加密的字符:askdjkasjdkasj(可能是md5双重加密)
  解密不了,服务端也只是存储的一段加密后的字符,用户登录经过一样的加密算法校验的是加密后的字符,不需要解密
  因为哈希加密算法不可逆的,找回密码是找回不了的,字符串解码不了的,只能重置密码
  加密传输为了安全

49:加密实例:

  一:京东登录网址:https://passport.jd.com/new/login.aspx

  chrome12——>Sources——>top(里面有js前端代码):发送请求代码打断点,然后跳转到console控制台

    输入pwd

            (function(){
                /**
                 * 锟斤拷锟斤拷锟斤拷锟斤拷
                 * @param pwd
                 */
                function getEntryptPwd(pwd){
                    var pubKey = $('#pubKey').val();
                    if(!pwd || !pubKey || !SysConfig.encryptInfo){
                        return pwd;
                    }
                    var encrypt = new JSEncrypt();
                    encrypt.setPublicKey(pubKey);
                    return encrypt.encrypt(pwd);
                }

    每次进行登录就会调用这段js代码进行前端的加密:encrypt.setPublicKey(pubKey);

    这是一个非对称加密算法:

    pubKey就是公钥:

      pubKey公钥的值(pubKey等同python变量):

        "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDC7kw8r6tq43pwApYvkJ5laljaN9BZb21TAIfT/

        vexbobzH7Q8SUdP5uDPXEBKzOjx2L28y7Xs1d9v3tdPfKI2LR7PAzWBmDMn8riHrDDNpUpJnlAGUqJG9ooPn8j

        7YNpcxCa1iybOlc2kEhmJn5uwoanQq+CA6agNkqly2H4j6wIDAQAB"

      明文admin通过公钥进行加密算法加密成了下面的字符串

        "ZUIEQ9tZnKFizRiZrhOp2VHnvrUHRQwwIIampAjACt9hlmSCPjJQzf/iET4bFyNJ9BkqdbkpMptD6yceflI/5V4f/j+
        PyGbpYZAyTNQm5nEmx1r/WCtDnKjgzfMcearDY72W9pW6UP9gLbxHC84GvV3nGKjufVnWEMldK1t8w6o="

      不停的加密加密后的密码是会变化的,公钥相同,明文相同(admin),加密后的结果会变得(因为加了个随机数)
      这就是用到了非对称得加密算法进行加密(明文+公钥---进行加密--得到密文)
        私钥

      这就是京东的加密算法

  二:浙江政务服务网

    https://puser.zjzwfw.gov.cn/sso/newusp.do?action=ssoLogin&servicecode=zjdsjgrbs

    如上的方法:

        var idnum;
        function login(isMark, password){
            login_flag=0;
            loginname = $('#loginname').val();
            var loginpwd = $('#loginpwd').val();
            var servicecode = $('#servicecode').val();
            var verifycode = $('#verifycode').val();
            var idType = $('#idType').val();
            var url = "newusp.do?rd="+Math.random();
            if(_loginname == ""){
                _loginname = loginname;
            }
        //MD5 加密
            if(isMark){
                loginpwd = RSA(loginpwd);                            :通过RSA这个函数进行加密的
            }else{
                loginpwd = password;
            }
            if(servicecode == "" || servicecode == null){
                servicecode = "zjdsjgrbs";
            }
                

    这段js代码进行的加密:

    很多网站都是RSA这种方式进行加密的,前端的加密算法就是js代码加密的
    RSA和MD5加密都是可以的:非对称加密会影响性能的,客户端js代码加密慢,

  三:md5加密实例

    前台输个密码明文123456,
    加盐:abc+明文+efg        把这段加盐的字符串进行一个md5加密  变成"sajdaksdjkasjdadasd"这样一个密文结果
    加盐:后台密文再加严        abc+密文+efg-----md5进行加密又变成一个密文(两次加密的密文存到数据库里面了)
      这样两次md5加盐加密就很安全了,

50:接口测试种碰到加密算法的实例

  python自己写个md5加密算法,python自带md5加密模块

  一:base64加密模块

    demo_base64.py                    #base64加密模块
        import base64
        str='sdfsdfsdf'
        pwd=base64.b64encode(str.encode('utf8'))         #加密算法编码一定指定编码,一般utf8  encode编码
        print(pwd)              #b'c2Rmc2Rmc2Rm'
        pwdde=base64.b64decode(pwd)                     #decode解码
        print(pwdde)

        #base64还可以编码图片    

  二:md5加密模块

	demo_md5.py						#md5加密模块
		import hashlib
		pwd='sdfsdfsdf'             #密码加密成md5
		md5=hashlib.md5()           #md5是一个类,实例化一个对象
		md5.update(pwd.encode("utf-8"))             #update方法进行编码,需要传递一个编码
					            #这时候需要加密的字符串转成转成utf8编码之后update到md5对象里面去了
		print(md5.hexdigest())          #hexdigest()这个方法得到md5的值
    
          #通过request爬md5网站获取md5加密的结果
		import requests
		import re
		url='https://md5jiami.bmcx.com/web_system/bmcx_com_www/system/file/md5jiami/data/?ajaxtimestamp=1611068552811'
		payload={"md5_zifu":"sdfsdfsdf"}
		resp=requests.post(url,data=payload)
		res=re.findall('style="word-wrap:break-word;">(.*?)</td></tr><tr><td ',resp.text,re.S)[2]
		print(res)

  三:rsa加密模块  

    demo_rsa.py                              #rsa加密
        #rsa 库不是python自带的,需要安装 pip install rsa
        import rsa
        str='sdfsdfsdf'                   #明文字符串,待加密的字符串
        #需要公钥,私钥
        #1:rsa.newkeys()实例化一个加密对象,1024里面传的是加密算法的长度
        pubkey,privkey=rsa.newkeys(1024)           #得到一个元组,公钥+私钥
        # print(pubkey,privkey)
        #2:用公钥进行加密
        pwd=rsa.encrypt(str.encode('utf8'),pubkey)  #第一个参数加密的字符串,第二个参数pubkey
        print(pwd.hex())                          #pwd.hex()打印加密后的结果

        #3,用私钥解密
        depwd=rsa.decrypt(pwd,privkey)          #第一个参数密码pwd,需要解密的东西,第二个参数是私钥
        print(depwd.decode('utf8'))

        #多次运行加密后的密问不同,解密结果唯一

51:mock:前后端分离的项目,

  1:根据需求文档,设计接口api,
  2:前端开发页面,后端开发接口,测试写测试用例 并行的
  3:前端需要展示数据,数据是从后端接口调过来的,不能写死,可能是一个json列表
    封装了json对象,后端接口没有开发完成--前端需要采用moko虚构技术,假设后端接口完成能够测试,
    能够调用,
    支付流程:
    支付--选择订单--选择支付方式--调用支付接口--返回结果页面
      假如支付接口还每开发完成,需要使用到moco技术

52:mock使用场景:
  1:接口未实现
  2:接口太复杂
  3:接口不稳定

53:mock最终目的为了提高工作效率,前端用的多,测试用的也很多,

54:mock介绍:

  实现mock的技术很多,这些技术中,可以分为两类,mock数据和mock服务:
  mock数据:即 mock 一个对象,写入一些预期的值,通过它进行自己想要的测试。常见的有:EasyMock、Mockito 、WireMock、JMockit。主要适用于单元测试。
  mock 服务:即mock 一个 sever,构造一个依赖的服务并给予他预期的服务返回值,适用范围广,更加适合集成测试。如 moco框架。
  Moco 是类似一个 Mock 的工具框架,一个简单搭建模拟服务器的程序库/工具,下载就是一个JAR包。有如下特点:
  只需要简单的配置 request、response 等即可满足要求
  支持 http、https、socket 协议,可以说是非常的灵活性
  支持在 request 中设置 Headers , Cookies , StatusCode 等
  对 GET、POST、PUT、DELETE 等请求方式都支持
  无需环境配置,有 Java 环境即可
  修改配置后,立刻生效。只需要维护接口,也就是契约即可
  支持多种数据格式,如 JSON、Text、XML、File 等
  可与其他工具集成,如 Junit、Maven等

55:下载moco:需要有java环境的支持

56:配置mock的json文件

  我们需要先编辑一个json文件,用来模拟不同的请求,返回不同的响应。

  新建一个文件,格式改为json,然后打开这个文件进行编辑,内容如下:

     [{
      "description":"demo",
      "request":{
       "uri":"/demo1"
       },
      "response":{
       "text":"Hello,demo1"
      }
     }]

  description是注释(描述),由于json无法写注释,所以提供了用这个key
  uri就是我们这个接口的统一资源标识符,可以根据模拟的接口自行定义
  response里的内容即为返回的值

  json文件编写的http协议三大部分如下:
    description               描述
    request               请求
      method             请求方法
      uri                uri是url的子集
      headers             请求头多个加s,content-type在这个里面
      cookies              缓存
      queries              queries查询此参数 get请求的name=xxx&psd=xxx 请求参数参数
      forms、json、text       请求消息体的内容(各种格式的内容)
    response 响应
      status                响应状态
      headers            响应头
      cookies
      text、json              响应消息体(内容)
      redirectTo              重定向(到什么网站)

  写的步骤:

    1:{}

    2:

demo1:约定URI            :凡是url=demo1的就能返回
     [{
      "description":"demo1=约定URI",                    #description类似注释的功能,声明这个接口描述
      "request":{
       "uri":"/demo1"
       },
      "response":{
       "text":"Hello,demo1"
      }
     }]
     
demo2:约定请求参数            :凡是请求参数是"key1":"abc","key2":"123"的就能返回,模糊匹配,
     [{
      "description":"demo2=约定请求参数",
      "request":{
       "queries":{
        "key1":"abc",
        "key2":"123"
         }
       },
      "response":{
       "text":"Hello,demo2"
      }
     }]

demo3:约定请求方法                            "method":"DELETE",不写method默认get请求,所有的DELETE请求返回demo3
 [{
  "description":"demo3=约定请求方法",
  "request":{
   "method":"DELETE"
   },
  "response":{
   "text":"Hello,demo3"
  }
 }]

demo4:约定请求头                        "Content-Type":"application/xml"
     [{
      "description":"demo4=约定请求头",
      "request":{
       "headers":{
        "Content-Type":"application/xml"
        }
       },
      "response":{
       "text":"Hello,demo4"
      }
     }]
     
demo5:约定请求体参数-form
     [{
      "description":"demo5=约定请求体参数-form",
      "request":{
       "forms":{
        "key1":"abc"
        }
       },
      "response":{
       "text":"Hello,demo5"
      }
     }]
     
demo6:约定请求体参数-json
     [{
      "description":"demo6=约定请求体参数-json",
      "request":{
       "headers":{
        "Content-Type":"application/json"
        },
       "json":{
         "key1":"value1",
         "key2":"value2"
        }
       },
      "response":{
       "text":"Hello,demo6"
      }
     }]

 

posted @ 2021-03-10 12:04  至高无上10086  阅读(165)  评论(0编辑  收藏  举报