• Python接口测试课程(第三天)-接口安全验证,参数化及断言


    目录

    Python接口测试课程(第一天)-Python基础
    Python接口测试课程(第二天)-接口测试快速实践
    Python接口测试课程(第三天)-接口安全验证,参数化及断言
    Python接口测试课程(第四天)-接口测试框架实现

    PDF下载:链接:https://pan.baidu.com/s/1x3_LYq23F_1LviGVbRNFXw 密码:xrcj

    更多学习资料请加添加作者微信:lockingfree获取

    第三天: Python接口测试(二)

    各种类型接口的测试

    GET请求接口

    requests.get(url=url, params=params)
    

    表单类型

    requests.post(url=url, data=data)
    

    REST类型

    requests.post(url=url, headers={"Content-Type": "application/json"}, data=json.dumps(data)
    

    上传文件

    requests.post(url=url, files={"file": open("1.jpg", "rb")})
    

    Session依赖

    session=requests.session(); session.post(...); session.post()
    

    接口依赖

    1. 接口依赖之动态中间值
    resp=requests.get(...);token=resp.split("=")[1];resp2=requests.post(....token...)
    

    验签接口

    import hashlib
    
    def md5(str):
        m = hashlib.md5()
        m.update(str.encode('utf8'))
        return m.hexdigest()  #返回摘要,作为十六进制数据字符串值
    
    def makeSign(params):
        if not isinstance(params, dict):
            print("参数格式不正确,必须为字典格式")
            return None
        if 'sign' in params:
            params.pop('sign')
        sign = ''
        for key in sorted(params.keys()):
            sign = sign + key + '=' + str(params[key]) + '&'
        sign = md5(sign + 'appsecret=' + appsecret)
        params['sign'] = sign
        return params
    
    data = makeSign(data);resp = requests.post(url=url, headers=headers, data=json.dumps(data))
    
    1. 接口依赖之Mock Server

    Mock和单元测试的桩(Stub)类似, 是通过建立一个模拟对象来解决依赖问题的一种方法.

    应用场景:
    1. 依赖第三方系统接口, 无法调试
    2. 所依赖接口尚未具备(前后端分离项目, 前端开发时,后端接口尚未开发完毕)
    3. 所依赖接口需要修改或不稳定
    4. 依赖接口较多或场景复杂, 所依赖接口不是主要验证目标的

    解决方法:
    1. 通过Mock.js/RAP/RAP2来动态生成, 模拟接口返回数据
    2. 自己使用Flask大家简单的Mock接口
    3. 使用Python自带的mock库

    ...
    

    SOAP接口

    pip install suds

    from suds.client import Client
    
    ip = '127.0.0.1'
    port = '5001'
    
    client = Client("http://%s:%s/?wsdl" % (ip, port))
    result = client.service.addUser("张790", "123456")
    print(result)
    

    XML-RPC接口

    import xmlrpc.client
    
    user = xmlrpc.client.ServerProxy('http://127.0.0.1:5002')
    print(user.getAll())
    

    参数化

    参数化是用来解决动态参数问题的

    数据文件参数化

    • csv数据文件
      • 优点:以逗号分隔,轻量级
      • 缺点:参数过多不便于区分
    import csv
    
    csv_data = csv.reader(open('data/reg.csv'))
    
    • config数据文件
      • 优点:方便支持多组数据,支持备注
    import configparser
    	cf=configparser.ConfigParser()
    	cf.read('data/reg.config', encoding='utf-8')
    	cf.sections()
    	cf.options(section)
    	cf.get(section,option)
    
    • json数据文件
      • 优点:REST接口常用数据格式,格式清楚,适用于多参数及含有嵌套参数
      • 缺点:不支持备注,多组数据不清晰
    import json
    with open('data/reg.json', encoding='utf-8') as f:
        json_data = json.loads(f)  #json_data为列表或字典
    
    json的序列化和反序列化
    需求:python的字典/列表等数据格式为内存对象,需要做存储(持久化)和进行数据交换
    
    序列化: 从内存对象到可存储数据, 方便存储和交换数据
    	json.dumps: 列表/字典/json->字符串 ```str_data = json.dumps(dict_data)```
    	json.dump: 列表/字典/json->数据文件 ```json.dump(dict_data, open(data_file, "w"))```
    反序列化: 从存储数据到内存对象
    	json.loads: 字符串->字典/列表```json.loads('{"a":1,"b":2}') #得到字典{"a":1,"b":2}```
    	json.load: json数据文档->列表/字典```dict_data = json.load(open('data.json'))```
    
    • excel数据文件
      • 优点:直观,构造数据方便
      • 缺点:嵌套数据不方便格式化

    pip install xlrd

    import xlrd
    
    wb=xlrd.open_workbook("data/reg.xlsx")
    sh=wb.sheet_by_index(0)
    sh=wb.sheet_by_name('Sheet1")
    sh.nrows
    sh.ncols
    sh.cell(x,y).value
    
    • xml数据文件
      • 优点:方便自定义多层结构,SOAP,RPC通用格式
    from xml.dom.minidom import parse
    dom=parse('data/reg.xml')
    root=dom.documentElement
    user_nodes=root.getElementsByTagName("user")
    user_node.getAttribute('title')
    user_node.hasAttribute('title')
    name_node=user_node.getElementsByTagName('name')[0]
    name=name_node.childNodes[0].data
    

    案例1: 自动执行excel用例并将结果回写excel

    数据文件: test_user.xlsx

    TestCase Url Method DataType Data Excepted Resp.text Status
    test_user_reg_normal /api/user/reg/ POST JSON {"name":"九小1","passwd": "123456"} resp.json()["code"]=="100000"
    test_user_login_normal /api/user/login/ POST FORM {"name":"九小1","passwd": "123456"} "成功" in resp.text
    import xlrd
    from xlutils.copy import copy
    import json
    import requests
    import sys
    
    base_url = "http://127.0.0.1:5000"
    
    def run_excel(file_name, save_file="result.xls"):
        wb=xlrd.open_workbook(file_name)
        sh=wb.sheet_by_index(0)
    
        wb2 = copy(wb)
        sh2 = wb2.get_sheet(0)
    
    
        for i in range(1,sh.nrows):
            url = base_url + sh.cell(i,1).value
            data = json.loads(sh.cell(i,4).value)
            headers = {}
            method = sh.cell(i,2).value
            data_type = sh.cell(i,3).value
            excepted = sh.cell(i,5).value
            if data_type.lower() == 'json':
                data = json.dumps(data)
                headers = {"Content-Type":"application/json"}
    
            if method.lower() == "get":
                resp = requests.get(url=url,headers=headers)
            else:
                resp = requests.post(url=url,headers=headers,data=data)
            if eval(excepted):
                status = "PASS"
            else:
                status = "FAIL"
            sh2.write(i,6, resp.text)
            sh2.write(i,7, status)
    
        wb2.save(save_file)
        print("保存成功")
            
            
    if __name__ == "__main__":
        if len(sys.argv)==2:
            run_excel(sys.argv[1])
        elif len(sys.argv)>2:
            run_excel(sys.argv[1],sys.argv[2])
        else:
            print("调用格式: python run_excel.py 用例文件 输出文件")
    

    保存脚本为run_excel.py, (最好和数据文件test_user.xlsx放在同一目录下), 在脚本所在目录打开命令行,运行

    python run_excel.py test_user.xlsx
    

    生成的result.xls预览

    TestCase Url Method DataType Data Excepted Resp.text Status
    test_user_reg_normal /api/user/reg/ POST JSON {"name":"九小1","passwd": "123456"} resp.json()["code"]=="100000" {"code":"100001","data":{"name":"u4e5du5c0f1","passwod":"e10adc3949ba59abbe56e057f20f883e"},"msg":"u5931u8d25uff0cu7528u6237u5df2u5b58u5728"} FAIL
    test_user_login_normal /api/user/login/ POST FORM {"name":"九小1","passwd": "123456"} "成功" in resp.text <h1>登录成功</h1> PASS

    随机数据参数化

    import random

    • 随机数
      • random.random(): 随机0,1
      • random.randint(0,100): 随机整数
      • random.randrange(0,100,2): 随机偶数
      • random.uniform(1,100): 随机浮点数
    • 随机选择
      • random.choice('abcdefg')
      • random.choice(['赵','钱','孙','李','周'])
    随机姓名的实现:
    #随机汉字: chr(random.randint(0x4e00, 0x9fbf))
    name=random.choice(['赵','钱','孙','李','周'])+chr(random.randint(0x4e00, 0x9fbf)
    
    • 随机样本
      • random.sample('abcdefg', 2)

    随机2个字符拼接: ''.join(random.sample('abcdefg',2)

    • 洗牌
      • random.shuffle([1, 2, 3, 4, 5, 6]): 随机改版列表数据

    断言/检查点

    断言/检查点是用来自动验证接口返回数据/业务操作的结果是否满足预期

    响应断言

    正则表达式

    • 元字符
      • . : 任意字符
      • d: 任意数字 - D: 任意非数字
      • w: 任意字符或数字 - W: 任意非字符及数字
      • s: 任意空白字符(包含空格或 等) - S: 任意非空白字符
      • ^: 匹配开头
      • $: 匹配结尾
      • []: 匹配其中任何一个字符
      • {n,m}: 匹配n-m个重复
        • : 匹配重复任意次(包含0次)
        • : 匹配重复至少一次
      • ? : 匹配0或1次
      • (): 分组,获取需要的部分数据
      • | : 或, 匹配多个pattern
      • 元字符: 取消元字符转义
    • 贪婪匹配及非贪婪匹配
    • 系统函数
      • re.findall(): re.S,支持跨行
      • re.match()
      • re.search()
      • re.sub()/re.subn()
      • re.complie()

    数据库断言

    从数据库读取数据,验证数据库数据是否符合预期

    • MySQL断言

    pip install pymysql

    1. 导入pymysql: import pymysql
    2. 建立数据库连接:
    conn = pymysql.connect(host='',port=3306,db='',user='',passwd='',charset='utf8')
    
    1. 从连接建立操作游标: cur=conn.cursor()
    2. 使用游标执行sql命令: cur.execute("select * from user")
    3. 获取执行结果:
      1. cur.fetchall(): 获取所有结果
      2. cur.fetchmany(3): 获取多条结果
      3. cur.fetchone(): 获取一条结果
    4. 关闭游标及连接(先关游标再关连接):cur.close();conn.close()
    • PostgreSQL

    pip install pyscopg2

    import pyscopg2
    conn=pyscopg2.connect(host='',port='',dbname='',user='',password='') # 注意是dbname, password
    cur=conn.curser()
    cur.execute("...")
    cur.fetchall()
    
    • Oracle

    pip install cx_Oracle

    ...
    
    • Mongodb

    pip install pymongo

    from pymongo import MongoClient
    
    conn = MongoClient('', 27017)
    db = conn.mydb
    my_set = db.test_set
    
    for i in my_set.find({"name": "张三"}):
        print(i)
    
    print(my_set.findone({"name": "张三"}))
    
    • Redis断言

    pip install redis

    import redis
    
    r = redis.Redis(host='192.168.100.198', port=6379, password='!QE%^E2sdf23RGF@ml239', db=0)
    print(r.dbsize())
    print(r.get('package'))
    print(r.hget('event_order_advance_008aea6a62115ec4923829ee09f76a9c18243f5d', 'user'))
    
    

    服务器断言

    pip install paramiko

    import paramiko
    
    client = paramiko.SSHClient()
    client.set_missing_host_key_policy(paramiko.AutoAddPolicy())  
    client.connect('192.168.100.241', 22, username='root', password='1234567', timeout=4)
    stdin, stdout, stderr = client.exec_command('cat /proc/meminfo')
    print(stdout.read())
    client.close()
    
    

    完整用例:

    import requests
    import pytest
    import json
    import hashlib
    import re
    
    
    def md5(string):
        m = hashlib.md5()
        m.update(string.encode('utf8'))
        return m.hexdigest()
    
    def makeSign(data):
        sign=''
        for key in sorted(data.keys()):
            sign += key + '=' + str(data[key]) + '&'
        sign += 'appsecret=NTA3ZTU2ZWM5ZmVkYTVmMDBkMDM3YTBi'
        data['sign'] = md5(sign)
        return data
    
    class DB():
        def __init__(self):
            # 建立连接
            self.conn = pymysql.connect(host='localhost',port=3307,user='root',passwd='',db='api',charset='utf8')
    
            # 建立一个游标
            self.cur = self.conn.cursor()
        
        def __del__(self):
            self.cur.close()
            self.conn.close()
    
        def getUserByName(self, name):
            self.cur.execute("select * from user where name='%s'" % name)
            return self.cur.fetchone()
    
        def checkUser(self, name, passwd):
            user = self.getUserByName(name)
            if user:
                if user[2] == md5(passwd):
                    return True
                else:
                    return False
            else:
                return None
                
    class TestUser(): # pytest识别不能用__init__方法
        base_url = 'http://127.0.0.1:5000'
        db = DB()
    
        def test_login(self):
            url = self.base_url + '/api/user/login/'
            data = {"name": "张三", "passwd": "123456"}
            resp = requests.post(url=url, data=data)
    
            #断言
            assert resp.status_code == 200
            assert '登录成功' in resp.text
    
        def test_reg(self):
            url = self.base_url + '/api/user/reg/'
            headers = {"Content-Type": "application/json"}
            data = {'name': '张10', 'passwd': '123456'}
            resp = requests.post(url=url, headers=headers, data=json.dumps(data))
    
            #断言
            assert resp.json()['code'] == '100000'
            assert resp.json()['msg'] == '成功'
            assert self.db.getUserByName('张10')
    
    
        def test_uploadImage(self):
            url = self.base_url + '/api/user/uploadImage/'
            files = {'file': open("复习.txt")}
            resp = requests.post(url=url, files=files)
    
            #断言
            assert resp.status_code == 200
            assert '成功' in resp.text
            # todo 服务器断言
    
        def test_getUserList(self):
            session = requests.session()
            login_url = self.base_url + '/api/user/login/'
            login_data = {"name": "张三", "passwd": "123456"}
            session.post(url=login_url, data=login_data)
    
            url = self.base_url + '/api/user/getUserList/'
            resp = session.get(url=url)
    
            #断言
            assert resp.status_code == 200
            assert '用户列表' in resp.text
            assert re.findall('w{32}',t2) != []
    
        def test_updateUser(self):
            session = requests.session()  # 接口依赖的接口需要用session
            get_token_url = self.base_url + '/api/user/getToken/'
            params = {"appid": '136425'}
            token_resp = session.get(url=get_token_url, params=params)
            assert re.findall('token=w{32}$')
            token = token_resp.text.split('=')[1]
    
            url = self.base_url + '/api/user/updateUser/?token=' + token
            data = {'name': '张三', 'passwd': '234567'}
            headers = {"Content-Type": "application/json"}
            resp = session.post(url=url, headers=headers, data=json.dumps(data))
    
            #断言
            assert resp.status_code == 200
            assert resp.json()['code'] == '100000'
            assert resp.json()['msg'] == '成功'
            assert self.db.checkUser('张三', '234567')
    
        def test_delUser(self):
            url = self.base_url + '/api/user/delUser/'
            headers = {"Content-Type": "application/json"}
            data = {'name': '张10', 'passwd': '123456'}
            data = makeSign(data)
            resp = requests.post(url=url, headers=headers, data=json.dumps(data))
    
            #断言
            assert resp.status_code == 200
            assert resp.json()['code'] == '100000'
            assert resp.json()['msg'] == '成功' 
            assert not self.db.getUserByName('张10')
    
    
    if __name__ == '__main__':
        t = TestUser()
        # t.test_updateUser()
        # t.test_updateUser()
        t.test_delUser()
        # pytest.main("-q test_user2.py")
    
  • 相关阅读:
    实验二
    个人简介及对未来的想法
    读《构建之法》心得体会
    作业2
    个人简介
    第六次作业
    第二次作业
    个人简历
    购物系统测试缺陷报告
    读《构建之法》心得体会
  • 原文地址:https://www.cnblogs.com/superhin/p/12737664.html
Copyright © 2020-2023  润新知