单元测试:pytest, python -> 直接对代码进行测试(可以直观有效的反馈错误,代价最小)
接口测试:requests -> 对服务器发送请求 (直接与服务器进行交互,代价中等)
UI测试:selenium, appium -> 对存在 UI 界面的应用进行测试(以用户视角进行测试,代价最大)
因为UI测试的代价太大,所以在项目测试中,接口的测试也是非常重要的。它可以较UI测试更快一步的发现bug,且修改bug的代价也较小。
接口测试与mock测试的区别:
接口测试与mock测试都是对数据进行测试,但接口测试是站在客户端的视角向服务端发送数据,并对服务端返回的数据进行判断。而mock测试是在客户端和服务端的中间,截取数据,并对数据进行处理。
总结:接口测试主要测试对象是服务端,mock测试主要测试对象是客户端(改造服务端返回的数据)。
是接口格式的一种规则
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来编写用例。所以我们这里只讲解接口自动化框架中特殊的一些方法。
原则:在自动化用例开发中,我们需要遵守一个很重要的原则。就是用例的独立性,即用例与用例之间最好不要有依赖性。所以在一个用例中需要的数据最好在前置方法中生成。且在完成用例后,需要在后置方法中,将该用例遗留的数据清除,保证这些数据不会影响到其他用例的执行。
接口请求数据生成的常用原理:
边界值,笛卡儿积等测试思想来生成数据。
考虑并发插件,使用pytest-xdist
在用例后加入 -n auto
,会根据 CPU 数(CPU逻辑数)进行并行。-n 3
指定3个进程来执行用例
注意:并行用例设计不要冲突,比如以下情况:
如何自行设计并行套件,可参考 pytest 的 hook:pytest_collection_modifyitems
在接口自动化的开发过程中,往往会出现多个接口需要传递相同的参数,例如: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'])
接口自动化也可以使用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'])