python自动化接口自动化:3.接口自动化测试框架理论

Posted new nm个对象

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了python自动化接口自动化:3.接口自动化测试框架理论相关的知识,希望对你有一定的参考价值。

一.接口测试理论

1.为什么需要接口测试

  • 单元测试:pytest, python -> 直接对代码进行测试(可以直观有效的反馈错误,代价最小)

  • 接口测试:requests -> 对服务器发送请求 (直接与服务器进行交互,代价中等)

  • UI测试:selenium, appium -> 对存在 UI 界面的应用进行测试(以用户视角进行测试,代价最大)

因为UI测试的代价太大,所以在项目测试中,接口的测试也是非常重要的。它可以较UI测试更快一步的发现bug,且修改bug的代价也较小。

2.接口测试与mock测试的区别

接口测试与mock测试的区别:

接口测试与mock测试都是对数据进行测试,但接口测试是站在客户端的视角向服务端发送数据,并对服务端返回的数据进行判断。而mock测试是在客户端和服务端的中间,截取数据,并对数据进行处理。

总结:接口测试主要测试对象是服务端,mock测试主要测试对象是客户端(改造服务端返回的数据)。

3.restful 结构

是接口格式的一种规则
Roy_Fielding:https://en.wikipedia.org/wiki/Roy_Fielding 1
论文:https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm 2

GET:获取资源. 参数信息放到请求头
POST:新建/更新资源. 参数信息放到请求体
PUT:更新资源
DELETE:删除资源

restful并不是一种协议,而是人们在开发过程中自发遵循的一种接口规范,它并不是强制性的。
实际开发中,大家一般只使用get和post来完成数据的增删查改

二.接口测试框架技术

接口测试框架技术跟前面讲的UI自动化测试框架使用的技术大体一样,比如配置文件,日志,测试报告,PO模式封装等。但也有许多不一样的地方,例如UI自动化主要使用selenium来编写用例,而接口自动化使用requests来编写用例。所以我们这里只讲解接口自动化框架中特殊的一些方法。

1.接口测试数据

原则:在自动化用例开发中,我们需要遵守一个很重要的原则。就是用例的独立性,即用例与用例之间最好不要有依赖性。所以在一个用例中需要的数据最好在前置方法中生成。且在完成用例后,需要在后置方法中,将该用例遗留的数据清除,保证这些数据不会影响到其他用例的执行。

(1)数据生成

接口请求数据生成的常用原理:
边界值,笛卡儿积等测试思想来生成数据。

(2)数据清理

  1. 当前接口受到脏数据影响,干扰正常功能。可以在测试用例后或者下一个用例前需要进行脏数据清洗。
  2. 以企业微信添加成员为例:当使用重复手机号,需要在前置方法中先添加一个相同手机号的成员。在用例结束后,在后置方法中将这两个成员数据都删除掉。
  3. 注意:尽量在测试后进行清理,而不是在下一个测试用例前。即一个用例的数据尽量不要影响到下一个用例
  4. 清理方法:
    1. 常用的清理方法:在后置方法中将用例创建的业务内容删除,端口删除,进程删除 ,文件清除,文件篡改恢复等等。
  5. 总结:实际开发过程中,自动化用例是团队一起维护的。自己用例的数据尽量不要遗留,以免影响到其他人的用例

2.接口测试提速

考虑并发插件,使用pytest-xdist

在用例后加入 -n auto,会根据 CPU 数(CPU逻辑数)进行并行。-n 3指定3个进程来执行用例

注意:并行用例设计不要冲突,比如以下情况:

  1. 并行用例中存在数据相同(随机数:时间种子)
  2. 并行用例端口相同
  3. 并行用例本地的文件相同:进程锁

如何自行设计并行套件,可参考 pytest 的 hook:pytest_collection_modifyitems

3.接口测试封装

(1)session封装

在接口自动化的开发过程中,往往会出现多个接口需要传递相同的参数,例如:token,cookie等。这时我们可以将这些公共的参数封装到session中,在使用session来访问这些接口。

实战:将企业微信的token封装到session,使用封装的session实现添加成员,删除成员接口的测试

import pytest
import requests
from requests import Session
class TestDemo:
    def setup_class(self):
        # 生成token
        url = 'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid=wwb0963a3a379de433&corpsecret=X-G73KTHX9is69sJcNbv-T80SyqL86EyvFUOpm4MTsg'
        r = requests.get(url=url)
        self.token = r.json()['access_token']
        # 实例化一个session
        self.session = Session()
        self.session.params['access_token'] = self.token # 将token封装到session内
    def test_add_remenber(self):
        """
        创建成员测试用例
        :return:
        """
        url = f'https://qyapi.weixin.qq.com/cgi-bin/user/create'
        # 请求数据体
        request_json = {
            "userid": "cndy",
            "name": "王大锤",
            "mobile": "13118175239",
            "department": [5]
        }
        r = self.session.post(url=url,json=request_json) # 使用session的post方法,发送post请求
        assert r.json()['errcode'] == 0
        assert r.json()['errmsg'] == 'created'

    def test_delete_remenber(self):
        """
        删除成员测试用例
        :return:
        """
        url = f'https://qyapi.weixin.qq.com/cgi-bin/user/delete?access_token={self.token}&userid=cndy'
        r = self.session.get(url=url) # 使用session的get方法,发送get请求
        assert r.json()['errcode'] == 0
        assert r.json()['errmsg'] == 'deleted'


if __name__ == "__main__":
    pytest.main(['-vs','test_demo.py'])

(2)PO模式进行封装

接口自动化也可以使用PO模式进行封装,将同一模块的接口封装在一个poject类中。

实战:使用PO模式封装成员查询接口,成员添加接口,成员删除接口。

第一步:将一些公共方法(例如:session)封装到basepage中。

import requests
from requests import Session


class BasePage:
    def __init__(self,corpid=None,corpsecret=None):
        # 生成session
        if corpid is None:
            my_corpid = 'wwb0963a3a379de433'
        else:
            my_corpid = corpid
        if corpsecret is None:
            my_corpsecret = 'X-G73KTHX9is69sJcNbv-T80SyqL86EyvFUOpm4MTsg'
        else:
            my_corpsecret = corpsecret

        r = requests.get(url=f'https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid={my_corpid}&corpsecret={my_corpsecret}')
        token = r.json()['access_token']
        self.session = Session()
        # 将token封装到session
        self.session.params['access_token'] = token

第二步:将通讯录相关接口封装到contact_page.py中

from typing import List

from api_frame.page_obj.base_page import BasePage


class ContactPage(BasePage):
    """
    通讯录管理相关接口封装
    """

    def add_remenber(self,userid:str,name:str,mobile:str,department:List[int],**kwargs):
        """
        添加成员接口封装
        :param userid: 成员UserID,必须参数。
        :param name: 成员名称。必须参数。
        :param mobile: 手机号码。企业内必须唯一,mobile/email二者不能同时为空
        :param department: 成员所属部门id列表,不超过100个
        :param kwargs:
        :return: 接口返回对象
        """
        json_data = {
            "userid": userid,
            "name": name,
            "mobile": mobile,
            "department": department
        }
        r = self.session.post(url='https://qyapi.weixin.qq.com/cgi-bin/user/create',json=json_data)
        return r

    def find_remenber(self,userid):
        """
        查询成员接口封装
        :param userid: 成员UserID。对应管理端的帐号,企业内必须唯一。不区分大小写,长度为1~64个字节
        :return: 接口返回对象
        """
        params = {
            "userid": userid
        }
        r = self.session.get(url='https://qyapi.weixin.qq.com/cgi-bin/user/get',params=params)
        return r

    def delete_remenber(self,userid):
        """
        删除成员接口封装
        :param userid: 成员UserID。对应管理端的帐号,企业内必须唯一。不区分大小写,长度为1~64个字节
        :return: 接口返回对象
        """
        params = {
            "userid": userid
        }
        r = self.session.get(url='https://qyapi.weixin.qq.com/cgi-bin/user/delete',params=params)
        return r

第三步:在test_contact.py中使用封装的通讯录接口编写测试用例

import pytest
from api_frame.page_obj.contact_page import ContactPage

class TestContact:
    def setup_class(self):
        self.contact = ContactPage()

    @pytest.mark.parametrize("userid,name,mobile,department",[('wsdsb_add001','wsdsb_add001','13118176666',[1]),
                                                              ('wsdsb_add002','wsdsb_add002','13118176667',[2,3]),
                                                              ('wsdsb_add003','wsdsb_add003','13118176668',[1,2,3,4,5]),
                                                              ('wsdsb_add004','wsdsb_add004','13118176669',[1]),
                                                              ('wsdsb_add005','wsdsb_add005','13118176661',[1])])
    def test_addremenber(self,userid,name,mobile,department):
        """添加成员接口测试用例"""
        # 添加成员接口测试
        r = self.contact.add_remenber(userid=userid,name=name,mobile=mobile,department=department)
        assert r.json()['errcode'] == 0
        assert r.json()['errmsg'] == 'created'
        # 清理数据
        self.contact.delete_remenber(userid=userid)


    @pytest.mark.parametrize("userid,name,mobile,department", [('wsdsb_find001', 'wsdsb_find001', '13118177666', [1]),
                                                               ('wsdsb_find002', 'wsdsb_find002', '13118177667', [2, 3]),
                                                               ('wsdsb_find003', 'wsdsb_find003', '13118177668',[1, 2, 3, 4, 5]),
                                                               ('wsdsb_find004', 'wsdsb_find004', '13118177669', [1]),
                                                               ('wsdsb_find005', 'wsdsb_find005', '13118177661', [1])])
    def test_findremenber(self,userid,name,mobile,department):
        """查询成员接口测试用例"""
        # 生成测试数据
        self.contact.add_remenber(userid=userid,name=name,mobile=mobile,department=department)
        # 查询成员接口测试
        r = self.contact.find_remenber(userid=userid)
        assert r.json()['errcode'] == 0
        assert r.json()['errmsg'] == 'ok'
        assert r.json()['userid'] == userid
        assert r.json()['name'] == name
        assert set(r.json()['department']) == set(department)
        assert r.json()['mobile'] == mobile
        # 清理测试数据
        self.contact.delete_remenber(userid=userid)



    @pytest.mark.parametrize("userid,name,mobile,department", [('wsdsb_delete001', 'wsdsb_delete001', '13118178666', [1]),
                                                               ('wsdsb_delete002', 'wsdsb_delete002', '13118178667', [2, 3]),
                                                               ('wsdsb_delete003', 'wsdsb_delete003', '13118178668',[1, 2, 3, 4, 5]),
                                                               ('wsdsb_delete004', 'wsdsb_delete004', '13118178669', [1]),
                                                               ('wsdsb_delete005', 'wsdsb_delete005', '13118178661', [1])])
    def test_deleteremenber(self,userid,name,mobile,department):
        """删除成员接口测试用例"""
        # 生成测试数据
        self.contact.add_remenber(userid=userid, name=name, mobile=mobile, department=department)
        # 成员删除接口测试
        r = self.contact.delete_remenber(userid=userid)
        assert r.json()['errcode'] == 0
        assert r.json()['errmsg'] == 'deleted'
if __name__ == "__main__":
    pytest.main(['test_contact.py','-n auto'])

以上是关于python自动化接口自动化:3.接口自动化测试框架理论的主要内容,如果未能解决你的问题,请参考以下文章

自动化框架-数据驱动

python自动化接口自动化:3.接口自动化测试框架理论

Python接口测试框架实战与自动化进阶

Python接口自动化测试

Python实现头条项目接口自动化测试

接口自动化 基于python+Testlink+Jenkins实现的接口自动化测试框架