1.审核接口的分析与代码编写
审核接口使用的是接口关联方法1(也就是所有关联或者依赖的接口全部作为被测接口的前置条件。setUp或setupClass)
审核接口分析
接口名称 | 请求方法 | 需要的内容 |
/member/login(借款人登录)
|
post | |
/member/login(管理员登录) | post | |
/loan/add
|
post |
member_id,token(借款人)
|
/loan/audit | patch | loan_id,token(管理员) |
因为要有两个角色的账号登录,所以要添加一个管理员的测试账号
secret_config.py代码修改如下:
# 借款人测试账号
user = {"mobile_phone": "15100002222", "pwd": "12345678"}
# 管理员测试账号
admin_user = {"mobile_phone": "15800002222", "pwd": "12345678"}
base.py代码修改如下:
import requests
from config import config, secret_config
from jsonpath import jsonpath
class APICase:
# 我们去访问项目当中接口需要用到的独立的方法或属性
@classmethod
def login(cls):
# 访问登录接口(普通用户登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.member_id = str(jsonpath(login_resp, '$..id')[0])
cls.token = jsonpath(login_resp, '$..token')[0]
return (cls.member_id, cls.token)
@classmethod
def admin_login(cls):
# 访问登录接口(管理员登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.admin_user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
cls.admin_token = jsonpath(login_resp, '$..token')[0]
return (cls.admin_member_id, cls.admin_token)
@classmethod
def add_project(cls, token, member_id):
# 访问添加项目的接口
json_data = {"member_id": "10",
"title": "测试项目55",
"amount": 100000,
"loan_rate": 10.0,
"loan_date_type": 2,
"loan_term": 30,
"bidding_days": 5}
headers = {"X-Lemonban-Media-Type": "lemonban.v2",
"Authorization": f"Bearer {token}"}
add_resp = requests.request(url=config.host + '/loan/add',
method='post',
headers=headers,
json=json_data).json()
cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
return add_resp
注意:
- 因为添加项目接口用到普通用户登录后的token和member_id,所以调用add_project方法时要传入token和member_id
- 添加项目接口记得要设置loan_id属性,提供审核接口使用
测试用例excel表格内容如下:
新建test_audit.py代码如下:
import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal
# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')
@ddt
class TestAudit(unittest.TestCase, APICase):
@classmethod
def setUpClass(cls) -> None:
# 普通用户登录
cls.login()
# 管理员登录
cls.admin_login()
cls.add_project(token=cls.token, member_id=cls.member_id)
def setUp(self) -> None:
# 添加项目
# self.add_project(token=self.token, member_id=self.member_id)
pass
@data(*excel_data)
def test_recharge(self, info):
log.info(f'正在测试{info}')
# info替换标记的测试数据
json_data = info['json']
headers = info['headers']
# 替换请求体
if "#loan_id#" in json_data:
json_data = json_data.replace("#loan_id#", self.loan_id)
# 替换请求头
if "#admin_token#" in headers:
headers = headers.replace("#admin_token#", self.admin_token)
# 转化成字典类型
json_data = json.loads(json_data)
headers = json.loads(headers)
expected = json.loads(info['expected'])
# 访问充值接口
recharge_resp = requests.request(url=config.host + info['url'],
method=info['method'],
headers=headers,
json=json_data).json()
# 断言
for key, value in expected.items():
self.assertEqual(value, recharge_resp[key])
运行结果:
场景一:测试用例共两条,一条成功一条失败(添加项目接口放到setup中)
场景二:测试用例共两条,一条成功/失败一条项目不在审核状态(添加项目接口放到setupClass中)
注意:根据不同的测试用例来决定add_project方法是放在setup中还是setupclass中
正则表达式替换标记的测试数据
修改base.py代码如下:
import requests
import re
from config import config, secret_config
from jsonpath import jsonpath
class APICase:
# 我们去访问项目当中接口需要用到的独立的方法或属性
@classmethod
def login(cls):
# 访问登录接口(普通用户登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.member_id = str(jsonpath(login_resp, '$..id')[0])
cls.token = jsonpath(login_resp, '$..token')[0]
# cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
return (cls.member_id, cls.token)
@classmethod
def admin_login(cls):
# 访问登录接口(管理员登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.admin_user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
cls.admin_token = jsonpath(login_resp, '$..token')[0]
return (cls.admin_member_id, cls.admin_token)
@classmethod
def add_project(cls, token, member_id):
# 访问添加项目的接口
json_data = {"member_id": "10",
"title": "测试项目55",
"amount": 100000,
"loan_rate": 10.0,
"loan_date_type": 2,
"loan_term": 30,
"bidding_days": 5}
headers = {"X-Lemonban-Media-Type": "lemonban.v2",
"Authorization": f"Bearer {token}"}
add_resp = requests.request(url=config.host + '/loan/add',
method='post',
headers=headers,
json=json_data).json()
cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
return add_resp
@classmethod
def replace_data(cls, string):
# 给指定的字符串替换成动态数据
result = re.finditer('#(.*?)#', string)
for i in result:
# i是匹配到的每个数据
old = i.group() # #member_id#
prop_name = i.group(1) # member_id
string = string.replace(old, str(getattr(cls, prop_name)))
return string
修改test_audit.py代码如下:
import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal
# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')
@ddt
class TestAudit(unittest.TestCase, APICase):
@classmethod
def setUpClass(cls) -> None:
# 普通用户登录
cls.login()
# 管理员登录
cls.admin_login()
cls.add_project(token=cls.token, member_id=cls.member_id)
def setUp(self) -> None:
# 添加项目
# self.add_project(token=self.token, member_id=self.member_id)
pass
@data(*excel_data)
def test_recharge(self, info):
log.info(f'正在测试{info}')
# 使用正则表达式替换json_data,headers中# #标记内容
json_data = info['json']
headers = info['headers']
json_data = self.replace_data(json_data)
headers = self.replace_data(headers)
# 转化成字典类型
json_data = json.loads(json_data)
headers = json.loads(headers)
expected = json.loads(info['expected'])
# 访问充值接口
recharge_resp = requests.request(url=config.host + info['url'],
method=info['method'],
headers=headers,
json=json_data).json()
# 断言
for key, value in expected.items():
self.assertEqual(value, recharge_resp[key])
运行结果:
总结:
- 在使用正则表达式APICase下的replace_data这个方法时,要进行两次替换,json_data和headers,因为请求头和请求体内都有标记
- 调用replace_data之前,必须在APICase设置同名属性,这里设置的是loan_id,admin_token
2.投资接口的分析与代码编写
投资接口使用的是接口关联方法2(业务流的测试方法,不需要做封装各种所需的接口做为前置条件)
投资接口分析
接口名称 | 请求方法 |
/member/login(投资人登录)
|
post |
/member/login(借款人登录) | post |
/member/login(管理员登录) | post |
/loan/add | post |
/loan/audit
|
patch |
/member/invest
|
post |
方法二是不需要依赖其他接口的,因此所需要的接口都会写到测试用例中
编写测试用例注意点:
- 单词一定注意别写错了,如investor_phon等
- 投资金额为空时,amount整个字段就可以不写,不用写成"amount":
- extractor是数据提取,为下一个接口需要做准备,当登录投资人接口时,拿到investor_member_id和investor_token;当登录借款人接口时,拿到loan_member_id和loan_token;当登录投资人接口时,拿到investor_member_id,loan_token;当登录管理员接口时,拿到admin_member_id和admin_token;当访问添加项目接口时,拿到loan_id
接着就开始编写代码了,因为我们涉及到三个角色,所以需要不同角色的账号,我这里把投资人跟借款人作为一个测试账号登录,所以secret_config.py的代码不需要修改,如果是三个账号登录,那就再增加一个。base.py模块的内容也不需要修改,只需要调用replace_data方法即可
测试投资接口代码如下:
import unittest
import requests
import json
from jsonpath import jsonpath
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log
# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')
@ddt
class TestInvest(unittest.TestCase, APICase):
@classmethod
def setUpClass(cls) -> None:
# 直接从配置文件中读取需要的数据,然后设置成APICase属性
cls.investor_phone = secret_config.user['mobile_phone']
cls.investor_pwd = secret_config.user['pwd']
cls.loan_phone = secret_config.user['mobile_phone']
cls.loan_pwd = secret_config.user['pwd']
cls.admin_phone = secret_config.admin_user['mobile_phone']
cls.admin_pwd = secret_config.admin_user['pwd']
@data(*excel_data)
def test_invest(self, info):
# 1.对数据进行预处理,接口访问,数据替换,字符串转化成字典
log.info(f'正在测试{info}')
# 使用正则表达式替换json_data,headers中# #标记内容
json_data = info['json']
headers = info['headers']
json_data = self.replace_data(json_data)
headers = self.replace_data(headers)
# 转化成字典类型
json_data = json.loads(json_data)
headers = json.loads(headers)
expected = json.loads(info['expected'])
extractor = {}
if info['extractor']:
extractor = json.loads(info['extractor'])
# 2.访问接口,得到接口的返回结果
resp = requests.request(url=config.host + info['url'],
method=info['method'],
headers=headers,
json=json_data).json()
# 3.响应结果当中读取接下来接口需要的数据,设置成##标记同名属性如investor_token
# 获取投资人登录的id和token
# 把id设置成AIPCase.investor_member_id
# 把token设置成AIPCase.investor_token
# 通过jsonpath获取$..id,$..token
# 设置属性,属性名称也要写到excel表格里面,如investor_member_id
for prcp_name, jsonpath_expression in extractor.items():
value = jsonpath(resp, jsonpath_expression)[0]
# 设置类属性
setattr(APICase, prcp_name, value)
# 4.断言
for key, value in expected.items():
self.assertEqual(value, resp[key])
运行结果:
注意:因为replace_data封装成了类方法,所以要替换的数据要写到类属性,也就是写到setupclass中,因为类方法不可以调用实例方法(把replace_data设置成实例方法时,要替换的数据就可以放到setup中了)
接着我们来继续把代码进行封装,把数据替换的代码封装到prev_data方法中,把访问接口封装到visit方法中,把数据提取封装到extract方法中,把断言封装到assert_all方法中
修改base.py代码如下:
import requests
import re
import json
from config import config, secret_config
from jsonpath import jsonpath
class APICase:
# 我们去访问项目当中接口需要用到的独立的方法或属性
@classmethod
def login(cls):
# 访问登录接口(普通用户登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.member_id = str(jsonpath(login_resp, '$..id')[0])
cls.token = jsonpath(login_resp, '$..token')[0]
# cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
return (cls.member_id, cls.token)
@classmethod
def admin_login(cls):
# 访问登录接口(管理员登录)
headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
json_data = secret_config.admin_user
login_resp = requests.request(url=config.host + 'member/login',
method='post',
headers=headers,
json=json_data).json()
cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
cls.admin_token = jsonpath(login_resp, '$..token')[0]
return (cls.admin_member_id, cls.admin_token)
@classmethod
def add_project(cls, token, member_id):
# 访问添加项目的接口
json_data = {"member_id": "10",
"title": "测试项目55",
"amount": 100000,
"loan_rate": 10.0,
"loan_date_type": 2,
"loan_term": 30,
"bidding_days": 5}
headers = {"X-Lemonban-Media-Type": "lemonban.v2",
"Authorization": f"Bearer {token}"}
add_resp = requests.request(url=config.host + '/loan/add',
method='post',
headers=headers,
json=json_data).json()
cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
return add_resp
@classmethod
def replace_data(cls, string):
# 给指定的字符串替换成动态数据
result = re.finditer('#(.*?)#', string)
for i in result:
# i是匹配到的每个数据
old = i.group() # #member_id#
prop_name = i.group(1) # member_id
string = string.replace(old, str(getattr(cls, prop_name)))
return string
def prev_data(self, info):
# 数据替换
# 使用正则表达式替换json_data,headers中# #标记内容
json_data = info['json']
headers = info['headers']
json_data = self.replace_data(json_data)
headers = self.replace_data(headers)
# 转化成字典类型
json_data = json.loads(json_data)
headers = json.loads(headers)
expected = json.loads(info['expected'])
extractor = {}
if info['extractor']:
extractor = json.loads(info['extractor'])
# 将替换和转化之后的数据重新设置回字典的key,value
info['json'] = json_data
info['headers'] = headers
info['expected'] = expected
info['extractor'] = extractor
return info
def visit(self, info):
# 访问接口
resp = requests.request(url=config.host + info['url'],
method=info['method'],
headers=info['headers'],
json=info['json']).json()
return resp
@classmethod
def extract(cls, info, resp):
# 数据提取
for prcp_name, jsonpath_expression in info['extractor'].items():
value = jsonpath(resp, jsonpath_expression)[0]
# 设置类属性
setattr(cls, prcp_name, value)
def assert_all(self, info, resp):
# 断言
for key, value in info['expected'].items():
self.assertEqual(value, resp[key])
def steps(self, info):
info = self.prev_data(info)
resp = self.visit(info)
self.extract(info, resp)
self.assert_all(info, resp)
修改投资接口代码如下:
import unittest
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log
# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')
@ddt
class TestInvest(unittest.TestCase, APICase):
@classmethod
def setUpClass(cls) -> None:
# 直接从配置文件中读取需要的数据,然后设置成APICase属性
cls.investor_phone = secret_config.user['mobile_phone']
cls.investor_pwd = secret_config.user['pwd']
cls.loan_phone = secret_config.user['mobile_phone']
cls.loan_pwd = secret_config.user['pwd']
cls.admin_phone = secret_config.admin_user['mobile_phone']
cls.admin_pwd = secret_config.admin_user['pwd']
@data(*excel_data)
def test_invest(self, info):
log.info(f'正在测试{info}')
self.steps(info)
运行结果:
总结:
什么时候用方法1,什么时候用方法2
方法1:setup操作,封装,每个具体的方法封装成函数,适合接口数据比较少,不嫌麻烦
方法2:适合接口数量较多,逼格高,实现难度大,通用的处理流程,调试起来比较麻烦
自动化测试需要注意的知识点:
- 读取数据库,读取excel,读取yaml,log日志
- 测试:单元测试框unittest/pytest
- 自动化测试的思想:ddt数据驱动,参数化
- 前面三个点几乎可以做任何的自动化测试任务(setup可以做接口依赖)
- 数据伪造,数据生成,faker,手机号码
- 接口关联
- 方法1:setup #数据替换#,if 或 正则表达式
- 方法2:数据提取,放在excel