封装思想

关键数据文件

配置数据:{project_name}.conf.yaml 存放配置信息,如环境、邮箱、数据库信息等

业务数据:{testcase_name}.api.yaml 存放接口信息,如:method、url、header、body等

用例数据:{testcase_name}.data.yaml 存放接口入参(测试用例)

过程数据:{testcase_name}.step.yaml 存放过程数据(借鉴httprunner),一般不用这个,可以忽略

封装思想

配置:根据配置⽂件获取初始配置和依赖

接口封装:接口调⽤进⾏抽象封装,类似PageObject效果

业务流程:业务⽤例设计,含有多个api形成的流程定义;不要再包含任何接口实现细节;断⾔

用例构建

使⽤package管理业务模块,使⽤class管理业务功能,使⽤method完成业务具体⾏为,使⽤配置⽂件读取初始配置,使⽤继承规划⽤例执⾏顺序,使⽤testcase完成测试⽤例的落地,使⽤assertion完成业务正确性校验,使⽤数据⽂件管理⽤例的数据驱动,使⽤jenkins完成持续集成。

具体实现

目录一览:

接口自动化基础(五)数据驱动_json

用例驱动

最简单,使用@pytest.mark.parametrize装饰器实现

步骤:

  1. 将用例写在yaml文件中
  2. 使用 yaml.safe_load读取文件
  3. 拿到读取的内容,使用@pytest.mark.parametrize传参

以管理企业标签为例:

# base_api.py   封装数据驱动框架,所有业务代码的父类
class BaseApi:

    @classmethod
    def yaml_load(cls, path):
    # 封装yaml文件的加载
        with open(path) as f:
            return yaml.safe_load(f)
            
    def jsonpath(self, path, r=None):
        if r is None:
            r = self.r.json()  # 拿到上一次使用format方法中的r
        return jsonpath(r, path)
# tag.py 业务代码
class  Tag(BaseApi):
  
  def create_tags(self, token, name):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/create'
      	method = 'post'
        params = {'access_token': token}
        headers = {'Content-Type': 'application/json;charset=utf8'}
        json = {'tag':{'name': name}}
        res = request.request(method = method, url = url, params = params, headers = headers, json=json)
        return res

    def get_tags(self, token):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/get'
        method = 'get'
        params = {'access_token': token}
        res = request.request(method = method, url = url, params = params)
        return self.api_send(self.data['get_tags'])

    def delete_tags(self, token, id):
        url = 'https://qyapi.weixin.qq.com/cgi-bin/tags/delete'
        method = 'post'
        params = {'access_token': token}
        headers = {'Content-Type': 'application/json;charset=utf8'}
        json = {'tag':{'id': id}}
        res = request.request(method = method, url = url, params = params, headers = headers, json=json)
        return res
# test_tag.py  case文件
import pytest
from api.tag import Tag
from api.token import Token

class TestTag:
    data = Tag.yaml_load("../data/test_tag.data.yaml")
    token = Token().get_token('xxx')
    t = Tag()

    # @pytest.mark.parametrize('name', ['黄铜', '白银', '黄金'])
    @pytest.mark.parametrize('name', data['test_delete'])
    def test_delete(self, name):
      	# 删除标签用例
        # 如果该标签已存在,就删除
        self.t.get_tags(token)
        x = self.tag.jsonpath(f'$..tag[?@.name=="{name}"]')  # jsonpath,json的元素定位方式,类似于xml中的xpth,这里我们用的是自己封装后的jsonpath方法(在BaseApi中),此时不用传r,因为上一句中我们调用的get方法中使用了format方法,r便自动传入了我们封装的jsonpath方法中;所以只要我们调用了format方法,并保证这个r是适合的,就不用传r
        if isinstance(x, list) and len(x) > 0:
            self.t.delete_tags(token, tag_id=[x[0]['id']])

        # 环境干净之后开始测试
        self.t.get_tags(token)
        path = '$..tag[?(@.name!="")]'
        size = len(self.tag.jsonpath(path))

        # 添加新标签并断言
        self.t.create_tags(token, name)
        self.t.get_tags(token)
        assert len(self.tag.jsonpath(path)) == size + 1

        # 删除标签
        tag_id = self.tag.jsonpath(f'$..tag[?(@.name == "{name}")]')[0]['id']
        self.t.delete_tags(token, tag_id)

        # 获取标签并断言
        self.t.get_tags(token)
        assert len(self.tag.jsonpath(path)) == size
# test_tag.data.yaml   yaml文件
"test_delete": ["demo1", "demo2", "demo3", "黄金"," ", "????" ]

业务驱动

将业务过程数据写在yaml方法中,包括请求方法,请求地址,请求参数,请求body,请求头等,在BaseApi中封装好通用的请求方法,业务类中直接调用,如下:

# BaseApi.py
class BaseApi:
    yaml_params = {}  # 定义好yaml_params,在业务方法中传入,以便yaml文件的内容替换
    
    def api_send(self, req: dict):  # req 传入加载好的yaml文件
        req['params']['access_token'] = self.get_token(self.secret)  # 给params的token赋值
        
        # 模板内容替换  yaml文件中的变量赋值
        raw = yaml.dump(req)
        for key, value in self.yaml_params.items():
            raw = raw.replace(f"${{{key}}}", repr(value))
        req = yaml.safe_load(raw)

        r = requests.request(
            req['method'],  # 传入yaml文件的method
            url=req['url'],  # 传入yaml文件的url
            params=req['params'],  # 传入yaml文件的params
            json=req['json']  # 传入yaml文件的json
        )
        self.format(r)
        return r.json()
# test_tag.api.yaml  业务过程的yaml文件
add:
  method: post
  url: https://qyapi.weixin.qq.com/cgi-bin/externalcontact/add_corp_tag
  params:
    access_token: ${token}
  json:
    group_id: ${group_id}
    tag:
      - name: ${name}
# tag.py 业务文件
class Tag(Token):

    def __init__(self):
        self.data = self.api_load('../data/test_tag.step.yaml')  # 把yaml文件中所有的api加载到当前类中 
        
    def get(self, group_id, name):
        self.yaml_params['group_id'] = group_id  # 调用时给BaseApi中的yaml_params传值,
        self.yaml_params['name'] = name          # 以便yaml文件的内容替换
        res = self.api_send(self.data['add'])   # 发请求
        return res

过程驱动

涉及到多个接口关联的用例,如删除标签用例,要先获取标签,再添加标签,最后才删除标签,实际上我们调用了三个业务方法,中间还可能多次调用,如下:
接口自动化基础(五)数据驱动_用例_02

这样写的话,用例中的代码太多了,这时可以将调用的方法和过程数据都封装在yaml文件中,在BaseApi中写好读取文件的代码,直接在用例中调用即可。主要理解getattr()方法的使用,执行当前调用对象的某一方法。

# base_api.py
def steps_run(self, steps: list):

    for step in steps:
        raw = yaml.dump(step)
        for key, value in self.params.items():
            raw = raw.replace(f"${{{key}}}", repr(value))
        step = yaml.safe_load(raw)

        if isinstance(step, dict):
            if "method" in step.keys():
                method = step['method'].split('.')[-1]
                getattr(self, method)(**step)
            if "extract" in step.keys():
                self.data[step["extract"]] = getattr(self, 'jsonpath')(**step)
            if "assertion" in step.keys():
                assertion = step["assertion"]
                if isinstance(assertion, str):
                    assert eval(assertion)
                if assertion[1] == "eq":
                    assert assertion[0] == assertion[2]

yaml文件如下:

steps:
- {method: tag.get}
- {path: "$..tag[?(@.name==${name})]", extract: before}
- {method: tag.add, name: "${name}" }
- {method: get}
- {path: "$..tag[?(@.name==${name})]", extract: after}
- {assertion: [1, eq, 1]}
- {assertion: "len([1,2]) < len([1])" }