接口自动化(四)接口关联的封装、统一请求封装、数据驱动、数据驱动断言

一、接口文档

https://api.apiopen.top/swagger/index.html#/

http://httpbin.org/

二、接口关联封装

目录结构:

test_api.py

import requests

class TestRequest():

    #全局变量(类变量),通过类名调用
    access_token=""  #定义全局变量access_token
    sess=requests.session()  #定义全局变量sess=requests.session()

    # 获取token
    def test_token(self):
        url = "https://api.apiopen.top/api/login"
        headers = {
            "accept": "application/json",
            "Content-Type": "application/json"
        }
        body = {
            "account": "1195528321@qq.com",
            "password": "000000"
        }
        res=TestRequest.sess.request(method="post",url=url,headers=headers,json=body)  #将所有的requests都替换为requests.session()就相当于都在一个会话中了
        print(res.json()["result"]["token"])
        TestRequest.access_token=res.json()["result"]["token"]  #将token的值赋给全局变量access_token

    # 文件上传
    def test_file(self):
        url = "http://httpbin.org/post"
        headers = {
            "accept": "application/json",
            "Content-Type": "multipart/form-data"
        }
        files = {
            "file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb")  # rb:转换为只读的二进制流
        }
        res = TestRequest.sess.request(method="post", url=url, headers=headers,files=files)  # 将所有的requests都替换为requests.session()就相当于都在一个会话中了
        print(res.status_code)

test_user.py  #由于在test_api.py定义了全局变量,而test.user.py想使用这些全局变量就得导入test_api.py模块下的类

import requests
from testcases.test_api import TestRequest  # 导入testcases包下的test_api模块下的TestRequest类


class TestUser():

    # 查询用户信息
    def test_user(self):
        url = "https://api.apiopen.top/api/getUserInfo"
        headers = {
            "accept": "application/json",
            "token": TestRequest.access_token  #使用TestRequest类的全局变量access_token
        }
        res = requests.request(method="get", url=url,headers=headers)
        print(res.json())

pytest.ini

[pytest]
addopts =
testpaths = ./
python_files = test_*.py
python_classes = Test*
python_functions = test

run.py

import pytest

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

执行结果:

testcases\test_api.py ..                                                 [ 40%]  #这里2个“.”表示执行了2个用例
testcases\test_user.py ...                                               [100%]  #这里3个“.”表示执行了3个用例

============================== 5 passed in 4.82s ==============================

为什么test_user.py中明明1个用例却执行了3个用例呢?这是由于类之间的继承特征导致的:test_user.py调用test_api.py下的类的时候把test_api.py类下的2个用例也执行了一遍

如何避免这种错误呢,可以去掉类中全局变量,用YAML文件代为保存全局变量:

目录结构:#新增一个空文件extract.yaml,新增一个公共函数目录common和公共模块yaml_util.py,然后test.api.py调用yaml_util.py模块将产生的全局变量access_token存储到extract.yaml,最后test_user.py就可以在extract.yaml中提取使用access_token了

yaml_util.py

import yaml  #需要安装模块:pip install pyyaml
import os

#读取
def read_yaml(key):
    with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f:  #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
        value=yaml.load(stream=f,Loader=yaml.FullLoader)  #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
        return value[key]  #value是字典类型的,可以通过关键字方式输出值

#写入
def write_yaml(data):
    with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
        yaml.dump(data,stream=f,allow_unicode=True)  #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式

#清空
def clear_yaml():
    with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
        f.truncate()  #清空文件内容

test_api.py  #1、导入yaml_util.py模块 2、删除全局变量access_token  3、修改test_token用例中保存token的方式(存到文件中) 

import requests
from common.yaml_util import write_yaml  #导入公共函数中“写入”yaml方法

class TestRequest():

    #全局变量(类变量),通过类名调用
    sess=requests.session()  #定义全局变量sess=requests.session()

    # 获取token
    def test_token(self):
        url = "https://api.apiopen.top/api/login"
        headers = {
            "accept": "application/json",
            "Content-Type": "application/json"
        }
        body = {
            "account": "1195528321@qq.com",
            "password": "000000"
        }
        res=TestRequest.sess.request(method="post",url=url,headers=headers,json=body)  #将所有的requests都替换为requests.session()就相当于都在一个会话中了
        print(res.json()["result"]["token"])
        write_yaml({"access_token": res.json()["result"]["token"]})  # 将{"access_token":token值}键值对写入extract.yaml文件

    # 文件上传
    def test_file(self):
        url = "http://httpbin.org/post"
        headers = {
            "accept": "application/json",
            "Content-Type": "multipart/form-data"
        }
        files = {
            "file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb")  # rb:转换为只读的二进制流
        }
        res = TestRequest.sess.request(method="post", url=url, headers=headers,files=files)  # 将所有的requests都替换为requests.session()就相当于都在一个会话中了
        print(res.status_code)

test_user.py  #1、删除导入test_api.py中全局变量的方法  2、导入yaml_util.py模块 3、修改test_edit_user用例中获取token的方式(从文件中读取)

import requests
from common.yaml_util import read_yaml  #导入公共函数中“读取”yaml方法


class TestUser():

    # 查询用户信息
    def test_user(self):
        url = "https://api.apiopen.top/api/getUserInfo"
        headers = {
            "accept": "application/json",
            "token": read_yaml("access_token")  #读取extract.yaml文件中key为access_token对应的值
        }
        res = requests.request(method="get", url=url,headers=headers)
        print(res.json())

run.py

import pytest

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

pytest.ini

[pytest]
addopts =
testpaths = ./
python_files = test_*.py
python_classes = Test*
python_functions = test

执行结果:

testcases\test_api.py ..                                                 [ 66%]  #执行了2条用例
testcases\test_user.py .                                                 [100%]  #执行了1条用例

============================== 3 passed in 5.71s ==============================

extract.yaml  #生成了一条token的值

access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjg5NCwiaWQiOjg5NCwiY3JlYXRlZEF0IjoiMjAyMy0wOC0wMyAwMToxMTowOSIsInVwZGF0ZWRBdCI6IjIwMjMtMDgtMDMgMDE6MTE6MDkiLCJkZWxldGVkQXQiOm51bGwsImFjY291bnQiOiIxMTk1NTI4MzIxQHFxLmNvbSIsImxldmVsIjowLCJleHAiOjE2OTc3MzA1NzgsImlzcyI6ImFwaV9vcGVuIiwibmJmIjoxNjk3MTI0Nzc4fQ.98qISsPB0YJDVPx86yyrybsy8aR0m94O8K6t-NWpLYA

以上代码仍有问题,由于write.yaml()选择追加的方式写入数据,多次执行后extract.yaml文件中会有多个access_token:[value]键值对,到时候read.yaml()取值的时候可能会报错,解决办法是增加前后置方法:在每次写入文件前清空extract.yaml文件

#演示:

目录结构:#增加前后置夹具

conftest.py

import pytest
from common.yaml_util import clear_yaml  #导入公共函数中“清空”yaml方法

#在所有接口请求之前执行
@pytest.fixture(scope='package',autouse=True)  #作用域是包,即testcases目录,并且自动执行
def clear_extract():
    clear_yaml()  #这是一个前置

三、统一请求封装

如果test_user.py中用例需要test_api.py中用例的cookie怎么办呢?可以调用test_api.py中全局变量sess=requests.session()使两个文件中的用例处于同一个会话(session),但是这样同样会因为类之间继承的问题导致test_api.py被调用时再执行一次,怎么解决呢,可以使用统一请求封装

将统一会话请求封装到一个模块中,然后所有测试用例去调用这个模块中的统一请求方法

目录结构:#新增文件

request_util.py

import requests

class RequestUtil():

    #全局变量(类变量),通过类名调用
    sess=requests.session()

    def send_request(self,method,url,**kwargs):
        method=str(method).lower()  #防止用户在传入get|post写为大写,可以用lower()转化为小写
        res=RequestUtil.sess.request(method=method,url=url,**kwargs)
        return res

test_api.py  #1、导入统一请求模块 2、删除sess=requests.session()全局变量 3、改用统一请求模块发送接口请求 4、因为使用了统一请求,因此无需import requests

from common.yaml_util import write_yaml  #导入公共函数中“写入”yaml方法
from common.request_util import RequestUtil  #导入公共函数中”统一session请求”方法

class TestRequest():

    # 获取token
    def test_token(self):
        url = "https://api.apiopen.top/api/login"
        headers = {
            "accept": "application/json",
            "Content-Type": "application/json"
        }
        body = {
            "account": "1195528321@qq.com",
            "password": "000000"
        }
        res=RequestUtil().send_request(method="post",url=url,headers=headers,json=body)  #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
        print(res.json()["result"]["token"])
        write_yaml({"access_token": res.json()["result"]["token"]})  # 将{"access_token":token值}键值对写入extract.yaml文件

    # 文件上传
    def test_file(self):
        url = "http://httpbin.org/post"
        headers = {
            "accept": "application/json",
            "Content-Type": "multipart/form-data"
        }
        files = {
            "file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb")  # rb:转换为只读的二进制流
        }
        res = RequestUtil().send_request(method="post", url=url, headers=headers,files=files)  #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
        print(res.status_code)

test_user.py  #1、导入统一请求模块 2、改用统一请求模块发送接口请求 4、因为使用了统一请求,因此无需import requests

from common.yaml_util import read_yaml  #导入公共函数中“读取”yaml方法
from common.request_util import RequestUtil  #导入公共函数中”统一session请求”方法

class TestUser():

    # 查询用户信息
    def test_user(self):
        url = "https://api.apiopen.top/api/getUserInfo"
        headers = {
            "accept": "application/json",
            "token": read_yaml("access_token")  #读取extract.yaml文件中key为access_token对应的值
        }
        res = RequestUtil().send_request(method="get", url=url,headers=headers)  #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
        print(res.json())

#其余文件不变

执行结果:

testcases\test_api.py ..                                                 [ 66%]
testcases\test_user.py .                                                 [100%]

============================== 3 passed in 5.51s ==============================

四、数据驱动

实际测试环境使用pytest的时候还需要建立数据驱动,所谓数据驱动就是把test_api.py、test_user.py中test_token、test_user等方法中写的method、url、headers、json等变量的内容封装到一个yaml文件中去调用,而不是直接写死到方法中,这个yaml文件就是数据驱动

数据驱动需要用到装饰器@pytest.mark.parametrize()

语法:@pytest.mark.parametrize(args_name,args_value)
#args_name 参数名,字符串,可自定义名称
#args_value 参数值(list,tuple,字典列表,字典元组),有多少个值那么测试用例就会执行多少次(以"-"为分隔符计算有多少个值)
#YAML介绍:

yaml是一种数据格式,主要用于配置文件或者编写用例

yaml只有两种数据:

  1.键值对

    key:(空格)value

  2.list

    用一个“-”表示一个列表

操作yaml的第三方模块是pyyaml,安装方法:pip install pyyaml

目录结构:#1、新增文件test_api-test_token.yaml 2、为方便演示删除test_user.py

 test_api-test_token.yaml

-  #这个"-"是每组数据的分隔符
  name: 获取接口统一鉴权码token  #本组数据名称
  request:  #请求内容
    method: post  #请求方法
    url: https://api.apiopen.top/api/login  #请求url
    headers:  #请求头
      accept: application/json
      Content-Type: application/json
    body:  #请求体
      account: 1195528321@qq.com
      password: '000000'  #000000加上引号表示是字符串,防止被识别成int,int只有一位0
  validate: None  #断言,这里选择不使用
#name、request、validate是数据驱动最基本的内容,当然这三个基本内容的名称可自定义

yaml_util.py  #增加一个读取数据驱动的方法read_testcases

import yaml  #需要安装模块:pip install pyyaml
import os

#读取
def read_yaml(key):
    with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f:  #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
        value=yaml.load(stream=f,Loader=yaml.FullLoader)  #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
        return value[key]  #value是字典类型的,可以通过关键字方式输出值

#写入
def write_yaml(data):
    with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
        yaml.dump(data,stream=f,allow_unicode=True)  #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式

#清空
def clear_yaml():
    with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
        f.truncate()  #清空文件内容

#读取数据驱动
def read_testcases(yaml_name):
    with open(os.getcwd()+'/testcases'+'/'+yaml_name,mode='r',encoding='utf-8') as f:
        value=yaml.load(stream=f,Loader=yaml.FullLoader)
        return value

test_api.py #为方便演示只保留一个用例 1、导入公共函数中“读取数据驱动”方法 2、导入pytest模块, 3、使用@pytest.mark.parametrize()获取请求参数

from common.yaml_util import write_yaml,read_testcases  #导入公共函数中“写入”yaml方法,“读取数据驱动”方法
from common.request_util import RequestUtil  #导入公共函数中”统一session请求”方法
import pytest

class TestRequest():

    # 获取token
    @pytest.mark.parametrize("args_name",read_testcases("test_api-test_token.yaml"))  #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
    def test_token(self,args_name):
        url = args_name["request"]["url"]  #从args_name中获取url
        headers = args_name["request"]["headers"]  #从args_name中获取请求头
        body = args_name["request"]["body"]  #从args_name中获取请求体
        res=RequestUtil().send_request(method=args_name["request"]["method"],url=url,headers=headers,json=body)  #从args_name中获取method
        print(res.json()["result"]["token"])
        write_yaml({"access_token": res.json()["result"]["token"]})  # 将{"access_token":token值}键值对写入extract.yaml文件

#其余文件不变

执行结果:

testcases\test_api.py .                                                  [100%]

============================== 1 passed in 0.22s ==============================

一个接口一般对应一个数据驱动yaml文件, 一个yaml可以以“-”为分隔符定义多组测试数据,@pytest.mark.parametrize可以根据有几组测试数据就把这个测试用例跑几遍

#演示

test_api-test_token.yaml

-  #第1组数据
  name: 获取接口统一鉴权码token  #本组数据名称
  request:  #请求内容
    method: post  #请求方法
    url: https://api.apiopen.top/api/login  #请求url
    headers:  #请求头
      accept: application/json
      Content-Type: application/json
    body:  #请求体
      account: 1195528321@qq.com
      password: '000000'  #000000加上引号表示是字符串,防止被识别成int,int只有一位0
  validate: None  #断言,这里选择不使用

-  #第2组数据
  name: 获取接口统一鉴权码token  #本组数据名称
  request:  #请求内容
    method: post  #请求方法
    url: https://api.apiopen.top/api/login  #请求url
    headers:  #请求头
      accept: application/json
      Content-Type: application/json
    body:  #请求体
      account: 971045897@qq.com
      password: '000000'  #000000加上引号表示是字符串,防止被识别成int,int只有一位0
  validate: None  #断言,这里选择不使用

conftest.py #因为每跑一组数据就会写入一条token,因此前置作用域改为“函数”

import pytest
from common.yaml_util import clear_yaml  #导入公共函数中“清空”yaml方法

#在所有接口请求之前执行
@pytest.fixture(scope='function',autouse=True)  #作用域是函数,即testcases目录,并且自动执行
def clear_extract():
    clear_yaml()  #这是一个前置

#其余文件不变

执行结果: #可以看到test_token测试用例被执行了2次,在实际使用时还可以将yaml中第2组数据写一个错误的值以验证测试用例准确性

testcases\test_api.py ..                                                 [100%]  #有两个“.”说明用例跑了两组数据

============================== 2 passed in 2.29s ==============================

五、数据驱动断言

断言assert用法:在python脚本中断言的作用是如果跟在assert后面条件为True继续执行下面内容,否则报错退出

#根据接口文档,返回状态码为200表示登录并成功获取token。因此可以加断言判断测试用例是否执行成功

目录结构:#不变

test_api-test_token.yaml

-  #第1组数据
  name: 获取接口统一鉴权码token  #本组数据名称
  request:  #请求内容
    method: post  #请求方法
    url: https://api.apiopen.top/api/login  #请求url
    headers:  #请求头
      accept: application/json
      Content-Type: application/json
    body:  #请求体
      account: 1195528321@qq.com
      password: '000000'  #000000加上引号表示是字符串,防止被识别成int,int只有一位0
  validate:   #断言
    eq: {code: 200}
    in: {code1: 200,code2: 2003}

test_api.py  #1、增加从数据驱动中读取的两个变量val、li_val 2、增加两个断言

from common.yaml_util import write_yaml,read_testcases  #导入公共函数中“写入”yaml方法,“读取数据驱动”方法
from common.request_util import RequestUtil  #导入公共函数中”统一session请求”方法
import pytest

class TestRequest():

    # 获取token
    @pytest.mark.parametrize("args_name",read_testcases("test_api-test_token.yaml"))  #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
    def test_token(self,args_name):
        url = args_name["request"]["url"]  #从args_name中获取url
        headers = args_name["request"]["headers"]  #从args_name中获取请求头
        body = args_name["request"]["body"]  #从args_name中获取请求体
        res=RequestUtil().send_request(method=args_name["request"]["method"],url=url,headers=headers,json=body)  #从args_name中获取method
        val=args_name["validate"]["eq"]["code"]  #从数据驱动中读取数值200
        li_val=[ args_name["validate"]["in"]["code1"],args_name["validate"]["in"]["code2"] ]  #从数据驱动中读取列表[200,2003]
        assert res.status_code == val  #断言:判断返回的状态码是否等于200,是就下一步,不是就报错退出
        assert res.status_code in li_val  #断言:判断返回的状态码是否是列表[200,2003]中的一个,是就下一步,不是就报错退出
        write_yaml({"access_token": res.json()["result"]["token"]})  # 将{"access_token":token值}键值对写入extract.yaml文件

#其余文件不变

执行结果:

testcases\test_api.py .                                                  [100%]

============================== 1 passed in 0.22s ==============================

这样一个简单的pytest测试框架就搭建完成了。

posted on 2023-05-13 19:26  vorn  阅读(845)  评论(0)    收藏  举报

导航