接口自动化

  • Post author:
  • Post category:其他


为了实现真正意义上的接口自动化,一般使用yaml文件存储测试用例,代码调用里面的数据来发送请求


@Controller
@RequestMapping("/send")
public class Login {
    @ResponseBody
    @RequestMapping("/login")
    public State login(String name,String password) {
        if ("123".equals(name)&&"123".equals(password)) {
            State state = new State();
            state.setState(200);
            return state;
        }
        State state = new State();
        state.setState(101);
        return state;
    }
}

1、实现ymal文件的读取,并发送get请求

创建data.xml文件

-  name: 登录接口
   base_url: http://localhost:8080/send/login
   request:
     method: get
     params: name=123&password=123
   validate:
     - equals: [ "status_code",200 ]

读取内容,并发送请求

import requests
import yaml
from selenium.webdriver.chrome import webdriver
from selenium.webdriver.common.by import By

import common.ymal


class TestCase:
    def read_yaml(self,path):
        # 打开yaml文件,读取方式为只读r,编码格式utf-8, 然后重命名为 f
        with open(path, mode='r', encoding='utf-8') as f:
            # 读取f文件流, 读取方式FullLoader,然后赋值给value
            value = yaml.load(stream=f, Loader=yaml.FullLoader)
            return value
    def test_login(self):
      requests_val=self.read_yaml("test_package/data.yml")
      base_url=requests_val[0]["base_url"]
      method=requests_val[0]["request"]["method"]
      params=requests_val[0]["request"]["params"]
      #发送请求
      res=requests.session().request(method=method,url=base_url,params=params)
      assert res.json()["state"]==200

method属性表示请求发送的类型

params:表示get请求传递的数据

json:表示json格式传递的数据

data:表示以form表单格式传递的数据

2、发送post请求:设置请求头的数据类型,使用对应方式传参

class TestCase:
    def read_yaml(self,path):
        # 打开yaml文件,读取方式为只读r,编码格式utf-8, 然后重命名为 f
        with open(path, mode='r', encoding='utf-8') as f:
            # 读取f文件流, 读取方式FullLoader,然后赋值给value
            value = yaml.load(stream=f, Loader=yaml.FullLoader)
            return value
    def test_login(self):
      requests_val=self.read_yaml("test_package/data.yml")
      base_url=requests_val[0]["base_url"]
      method=requests_val[0]["request"]["method"]
      data=requests_val[0]["request"]["data"]
      hearder=requests_val[0]["request"]["hearders"]
      res=requests.session().request(method=method,url=base_url,data=data,headers=hearder)
      assert res.json()["state"]==200

3、实现文件上传


@Controller
@RequestMapping("/send")
public class upload {
    @PostMapping("/upload")
    public void Upload(@RequestParam("file") MultipartFile file, HttpServletResponse response) throws IOException {
        String name = file.getOriginalFilename();
        response.setStatus(200);
        File newfile = new File("C:\\Users\\30283\\Desktop\\novel\\photo\\" ,name);
        file.transferTo(newfile);
    }
}

一般需要设置Content-Type: multipart/form-data,但是文件上传时会自动添加,如果自己添加了可能会报错

以files关键字来传递文件数据

 
-  name: 文件上传接口
   base_url: http://localhost:8080/send/upload
   request:
     method: post
     files:
       C:\\Users\\30283\Pictures\\picture\\七七.png
   validate:
     - equals: [ "status_code",200 ]
class TestCase:
    def read_yaml(self,path):
        # 打开yaml文件,读取方式为只读r,编码格式utf-8, 然后重命名为 f
        with open(path, mode='r', encoding='utf-8') as f:
            # 读取f文件流, 读取方式FullLoader,然后赋值给value
            value = yaml.load(stream=f, Loader=yaml.FullLoader)
            return value
    def test_login(self):
      requests_val=self.read_yaml("test_package/data.yml")
      base_url=requests_val[0]["base_url"]
      method=requests_val[0]["request"]["method"]
      file_path=requests_val[0]["request"]["files"]
      file_data = {'file': ("七七.png", open(file_path, 'rb'), 'image/png')}
      #以二进制流的形式打开文件
      requests.session().request(method=method,url=base_url,files=file_data)

把目标文件以open打开,然后存储到变量,并且使用files参数指明请求的参数名称、上传文件的类型、以及上传文件的路径。

4、实现接口关联

比如淘宝:只有用户登录之后,才可以加入商品到购物车,这时就要设置接口关联,使用新的ymal文件存储关联数据


@Controller
@RequestMapping("/set")
public class SetCookie {
    @RequestMapping("/setcookie")
    @ResponseBody
    public String set(HttpServletRequest request,HttpServletResponse response) {
        return "{\"token\":\""+UUID.randomUUID()+"\"}";
    }
}

封装ymal文件操作方法

# 文件说明:yaml文件的读取与写入
import yaml

#读取ymal文件的信息
def read_yaml(path):
    # 打开yaml文件,读取方式为只读r,编码格式utf-8, 然后重命名为 f
    with open(path, mode='r', encoding='utf-8') as f:
        # 读取f文件流, 读取方式FullLoader,然后赋值给value
        value = yaml.load(stream=f, Loader=yaml.FullLoader)
        return value

#给yaml文件追加信息
#为什么追加:让这个文件可以存储多个关联信息
def write_yaml(path, data):
    with open(path, mode='a', encoding='utf-8') as f:
        # 写入的数据从data传入
        yaml.dump(data=data, stream=f,allow_unicode=True)  #allow_unicode=True支持中文

#将这个clear_yaml配置到conftest中,这样可以清空之前的关联信息
# 清空yaml文件内容
def clear_yaml(path):
    # 打开yaml文件
        with open(path, mode='w', encoding='utf-8') as f:
            # 清空yaml文件
          f.truncate()


#读取文件的某一个数据
def read_value(path,key):
    # 打开yaml文件,读取方式为只读r,编码格式utf-8, 然后重命名为 f
    with open(path, mode='r', encoding='utf-8') as f:
        # 读取f文件流, 读取方式FullLoader,然后赋值给value
        value = yaml.load(stream=f, Loader=yaml.FullLoader)
        return value[key]

在conftest.py文件中,让clear_yaml()方法设置自动使用

@pytest.fixture(scope="session",autouse=True)
def clear():
    common.ymal.clear_yaml("C:/python学习/python自动化测试/temp.ymal")

class TestCase:
    def test_login(self):
      requests_val=  common.ymal.read_yaml("test_package/data.yml")
      base_url=requests_val[0]["base_url"]
      method=requests_val[0]["request"]["method"]
      res=requests.session().request(url=base_url,method=method)
      #将返回的中间数据存储到ymal文件中
      common.ymal.write_yaml("temp.ymal", res.json())

    def test_check(self):
        print(common.ymal.read_value("temp.ymal","token"))

token保留在中间文件中,并且每次保留的都是最新值

5、可以使用extract关键字提取接口的返回结果

@Controller
@RequestMapping("/set")
public class SetCookie {
    @RequestMapping("/setcookie")
    @ResponseBody
    public String set(HttpServletRequest request,HttpServletResponse response) {
        HttpSession session=request.getSession(true);
        UUID val=UUID.randomUUID();
        session.setAttribute("token",val);
        return "{\"token\":\""+val+"\"}";
    }
}

@Controller
@RequestMapping("/get")
public class GetCookie {
    @ResponseBody
    @RequestMapping("/getcookie")
    public String get(String token,HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("token") != null)
            return "ok";
        return "error";
    }
}

1、yaml文件新建extract参数,里面填写需要从当前接口的出参里提取的字段。提取字段有两种方法,第一种是正则提取,第二种是json数据提取

2、写文件

判断yaml文件的格式,是否有extract参数,接着判断提取的字段是正则提取还是json提取。对请求进行封装,将字段获取之后写入到extract.yaml文件即可。

安装插件:pip.exe install jsonpath -i http://pypi.douban.com/simple/ –trusted-host pypi.douban.com SomePackage

通过JSONPath表达式,使得从多层嵌套JSON数据中提取数据变得非常简单。

#规范YAML测试用例
#1、要求包含一级关键字 name,request,validate 
#2、在request一级关键字下,包含method和url字段
#如果是get请求,通过params关键字传参
# 如果是post请求 传json格式,使用json关键字传参
#如果是post请求 传表单格式,使用data关键字传参
#如果是files请求,使用file传参

1、在根目录下,创建extract.yaml文件

2、创建py文件,封装方法,实现对ymal文件格式的验证和方法的请求,并实现extract提取关键字

#!/usr/bin/python
# -*- coding: UTF-8 -*
import json
import re

import jsonpath
import requests

import common.ymal
from debug_talk import DebugTalk


class Unifiedrequest:

    def __init__(self,two,obj):
        #self.url = common.ymal.read_value("base", two)  #这里,从文件获取base,来获取url前面的公共部分
        self.url="" #这里是因为我的url就是全部路径
        #获取一个对象
        self.obj=obj
        #传入的是Debug_Talk文件的类对象,可以调用这个类里面的方法

    #数据替换,data为获取到${}中的数据
    #数据类型可能(string,int,float,list,dict),需要先数据转换
    def read_token(self,data):
        if data:
            #保存原数据类型
            data_type=type(data)
            #数据类型转换
            if isinstance(data,dict) or isinstance(data,list):
                str_data=json.dumps(data)
            else:
                str_data=str(data)
            for ce in range(1,str_data.count("${")+1):
                if "${" in str_data and "}" in str_data:
                    index=str_data.index("${")
                    indexend=str_data.index("}",index)
                    old_value=str_data[index:indexend+1]
                    #获取对象属性
                    fun_name=old_value[2:old_value.index("(")]
                    fun_values=old_value[old_value.index("(")+1:old_value.index(")")]
                    fun_values_new=fun_values.split(",")
                    #*fun_values_new 解包 列表
                    if fun_values_new != ['']:
                        #getattr() 函数用于返回一个对象属性值。
                    	#self.obj为对象名称
                        new_value=getattr(self.obj,fun_name)(*fun_values_new)
                        str_data=str_data.replace(old_value,str(new_value))
                    else:
                        new_value = getattr(self.obj,fun_name)()
                        str_data = str_data.replace(old_value, str(new_value))
            if isinstance(data,dict) or isinstance(data,list):
                data=json.loads(str_data)
            else:
                data=data_type(str_data)
            print(data)
        return data

    #yaml文件封装
    def send_yaml(self,arges_name):
        yaml_key = arges_name.keys()
        if "name" in yaml_key and "request" in yaml_key:
            yaml_request=arges_name["request"]
            request_key = yaml_request.keys()
            if "method" in request_key and "url" in request_key:
                method=yaml_request.pop("method")
                url=yaml_request.pop("url")
                res=self.send_request(method,url,**arges_name["request"])
                res_text=res.text
                #状态码
                res_status=res.status_code
                res_json=""
                try:
                    res_json = res.json()
                except Exception as e:
                    print("jsonpath数据类型必须是json格式")
                if "extract" in yaml_key:
                    for key,values in arges_name["extract"].items():
                        if "(.*?)" in values or "(.+?)" in values:
                            zz_value=re.search(values,res_text)
                            if zz_value:
                                common.ymal.write_yaml("extract.yaml",{key:zz_value.group(1)})
                        else:
                            jp_values=jsonpath.jsonpath(res_json,values)
                            if jp_values:
                                common.ymal.write_yaml("extract.yaml",{key:jp_values[0]})

            else:
                print("request中缺少关键字段:method,url")
        else:
            print("yaml文件第一阶段缺少关键字段:name,request")

    session=requests.session()
    def send_request(self,method,url,**kwargs):
        url = self.url + self.read_token(url)
        for key,value in kwargs.items():
            if key in ["params","json","data","headers"]:
                kwargs[key]=self.read_token(value)
            elif key == "files":
                for file_key,file_value in value.items():
                    value[file_key] = open(file_value,"rb")
        res=Unifiedrequest.session.request(method,url,**kwargs)
        print(res.text)
        return res

3、请求yaml文件如下,使用extract关键字,提取某一个值


-  name: 设置cookie接口
   request:
     method: post
     url: http://localhost:8080/set/setcookie
   validate:
     - equals: ["status_code",200 ]
   extract:
     token: '"token":"(.*?)"'
#     或者  $.token

-  name: 获取关联的token值
   request:
      method: post
      url: http://localhost:8080/get/getcookie
      data:
        token: ${read_value(token)}
  
   validate:
        - equals: [ "status_code",200 ]

4、设置conftest.py的夹具方法,清除extrcat.yaml保留的token值

import pytest
from selenium import  webdriver
import common.ymal

@pytest.fixture(scope="session",autouse=True)
def clear():
    common.ymal.clear_yaml("extract.yaml")

5、运行

import pytest
import requests
import yaml
from selenium.webdriver.chrome import webdriver
from selenium.webdriver.common.by import By

import common.ymal
import debug_talk
from common.package import  Unifiedrequest
class TestCase:
        # debug_talk.DebugTalk()是在更目录创建的动态参数类方法
        @pytest.mark.parametrize("args_name", common.ymal.read_yaml("test_package/data.yml"))
        def test_post_tags(self, args_name):
            Unifiedrequest("base_url", debug_talk.DebugTalk()).send_yaml(args_name)






6、热加载

热加载:就是在代码运行的过程中动态的调用python中的方法达到获得动态参数的目的

在工程目录下,创建debug_talk文件,在里面写一个获取随机数的方法

#!/usr/bin/python
# -*- coding: UTF-8 -*
import random
import time

import common.ymal
from common import ymal


class DebugTalk:

    # 获得随机数
    def get_randon_number(self, min, max):
        return random.randint(int(min), int(max))

    def get_time(self, key):
        return int(time.time())

    # 读取鉴权码yaml文件--token.yaml
    def read_value(self,key):
        return common.ymal.read_value("extract.yaml",key)

封装文件内修改一部分:

                if "${" in str_data and "}" in str_data:
                    index=str_data.index("${")
                    indexend=str_data.index("}",index)
                    old_value=str_data[index:indexend+1]
                    #获取对象属性
                    fun_name=old_value[2:old_value.index("(")]
                    fun_values=old_value[old_value.index("(")+1:old_value.index(")")]
                    fun_values_new=fun_values.split(",")
                    #*fun_values_new 解包 列表
                 if fun_values_new != ['']:
                        #getattr() 函数用于返回一个对象属性值。
                        new_value=getattr(self.obj,fun_name)(*fun_values_new)
                        str_data=str_data.replace(old_value,str(new_value))
                    else:
                        new_value = getattr(self.obj,fun_name)()
                        str_data = str_data.replace(old_value, str(new_value))

然后,可以在ymal文件中动态获取这个参数


-  name: 设置cookie接口
   request:
     method: post
     url: http://localhost:8080/set/setcookie
   validate:
     - equals: ["status_code",200 ]
   extract:
     token: '"token":"(.*?)"'
#     或者  $.token

-  name: 获取关联的token值
   request:
      method: post
      url: http://localhost:8080/get/getcookie
      data:
        token: ${read_value(token)}

   validate:
        - equals: [ "status_code",200 ]

-  name: 获取随机数字
   request:
      method: post
      url: http://localhost:8080/send/login
      data:
        username=${get_randon_number(100,999)}&password=${get_randon_number(1000,9999)}
   validate:
        - equals: [ "status_code",200 ]

参考


pytest框架之热加载_python 热加载_Beck_k的博客-CSDN博客



版权声明:本文为m0_58342797原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。