文章目录
一:接口自动化实现思想
代码分层思想
测试脚本层:调用接口,按照响应数据,断言完成测试。
1.重点关注测试数据准备和断言
2.直接调用接口对象层发送请求
接口对象层:发送http请求,访问待测接口,返回响应数据。
1.根据接口api文档封装
2.重点关注如何调用接口
3.请求参数从测试脚本层传递
4.接口响应结果返回给脚本层
将变化的当做参数传入,不变的部分封装成类或者方法。
二、接口实现细节
1.登录接口封装前后
接口封装实现:
import requests
class TpshopLoginApi(object):
# 获取验证码
@classmethod
def get_verify(cls,session):
session.get(url='http://wwwww')
# 登录
@classmethod
def login(cls,session,login_data):
resp=session.post(url='http://www',data=login_data)
return resp
if __name__ == '__main__':
# 测试封装的接口对象层,功能是否正确
session=requests.Session()
TpshopLoginApi.get_verify(session)
login_data={"username":"wwww","password":"3434"}
resp=TpshopLoginApi.login(session,login_data)
print("登录结果:",resp.json())
接口调用测试:
from py_03_thshopApi import TpshopLoginApi
import requests
class TestTPShopLogin(object):
# 定义 测试方法-登录成功
def test01_login_success(self):
session=requests.Session()
# 发送验证码
TpshopLoginApi.get_verify(session)
# 发送登录
login_data={"username":"22","password":"8888"}
resp=TpshopLoginApi.login(session,login_data)
# 打印响应结果
print("登录成功:",resp.json())
# 添加断言
assert 200==resp.status_code
assert 1==resp.json().get("status")
assert "登录成功" in resp.json().get("msg")
# 密码错误
def test02_pwd_err(self):
session = requests.Session()
# 发送验证码
TpshopLoginApi.get_verify(session)
# 发送登录
login_data = {"username": "22", "password": "8888"}
resp = TpshopLoginApi.login(session, login_data)
# 打印响应结果
print("登录失败:", resp.json())
# 添加断言
assert 200 == resp.status_code
assert -2 == resp.json().get("status")
assert "登录失败" in resp.json().get("msg")
# 验证码错误
def test03_verify_err(self):
session = requests.Session()
# 发送验证码
TpshopLoginApi.get_verify(session)
# 发送登录
login_data = {"username": "22", "password": "8888"}
resp = TpshopLoginApi.login(session, login_data)
# 打印响应结果
print("登录失败:", resp.json())
# 添加断言
assert 200 == resp.status_code
assert 0 == resp.json().get("status")
assert "验证码错误" in resp.json().get("msg")
接口调用,优化后:(对公用的方法进行抽离,封装)
from py_03_thshopApi import TpshopLoginApi
import requests
class TestTPShopLogin(object):
# 类属性调用 可以用类名、实例、self、cls,均可引用
session=None
def setup(self):
session=requests.Session()
# 发送验证码
TpshopLoginApi.get_verify(self.session)
# 定义 测试方法-登录成功
def test01_login_success(self):
# 发送登录
login_data={"username":"22","password":"8888"}
resp=TpshopLoginApi.login(self.session,login_data)
# 打印响应结果
print("登录成功:",resp.json())
# 添加断言
assert 200==resp.status_code
assert 1==resp.json().get("status")
assert "登录成功" in resp.json().get("msg")
# 密码错误
def test02_pwd_err(self):
# 发送登录
login_data = {"username": "22", "password": "8888"}
resp = TpshopLoginApi.login(self.session, login_data)
# 打印响应结果
print("登录失败:", resp.json())
# 添加断言
assert 200 == resp.status_code
assert -2 == resp.json().get("status")
assert "登录失败" in resp.json().get("msg")
# 验证码错误
def test03_verify_err(self):
# 发送登录
login_data = {"username": "22", "password": "8888"}
resp = TpshopLoginApi.login(self.session, login_data)
# 打印响应结果
print("登录失败:", resp.json())
# 添加断言
assert 200 == resp.status_code
assert 0 == resp.json().get("status")
assert "验证码错误" in resp.json().get("msg")
setup():每个方法运行前都会运行一次
setup_class():每个类运行之前都会运行一次
2.定义项目目录结构
api:定义封装被测接口(接口对象层 py文件夹)
scripts:定义测试用例脚本(测试脚本层 py文件夹)
data:存放测试数据文件(测试数据文件 文件)
report:存放生成的测试报告(文件)
common:存放通用工具类
config.py:配置的全局变量
pytest.ini:pytest配置
3.pytest.ini
定义pytest.ini文件:
运行的时候,可以直接在pycharm终端运行,使用命令pytest
[pytest]
addopts = -s --html=./report/ihrmReport.html --self-contained-html
testpaths = ./scripts
python_files = test*.py
python_classes = Test*
python_functions = test*
4.断言
断言 定义为一个通用的方法(common目录)
def common_assert(resp, status_code, success, code, message):
assert status_code == resp.status_code
assert success is resp.json().get("success")
assert code == resp.json().get("code")
assert message in resp.json().get("message")
5.参数化
数据参数化:
创建一个新的json文件,将接口用例需要传入的参数,使用json格式列举,在调用的时候,读取json文件。
读取文件 parameterize
1.参数化的核心:数据驱动->用数据驱动测试用例的执行(几条数据,用例执行多少次),针对一个接口,只写一个测试方法,用一份测试数据文件,管理各个用例的测试数据。
读取json文件的时候,建议使用绝对路径:
1.在config.py文件中,添加全局变量,获取项目目录BASE_DIR=os.path.dirname( _ file _ )
2.拼接json文件的绝对路径 BASE_DIR+“/data/login_data.json”
3.使用绝对路径,传入json文件读取函数 read_json_data
import os
# 定义全局变量,获取项目路径
BASE_DIR = os.path.dirname(__file__)
print(BASE_DIR)
import pytest
from api.ihrm_login_api import LoginApi
from common.assert_tools import common_assert
from common.logging_use import init_log_config
from common.read_json_file import read_json_data
from config import BASE_DIR
class TestLoginParams(object):
# 读取json文件,获取[(),(),()]格式数据
data = read_json_data(BASE_DIR + "/data/login_data.json")
@pytest.mark.parametrize("desc, req_data, status_code, success, code, message", data)
# 定义 通用测试方法 - 测试ihrm登录接口
def test_login(self, desc, req_data, status_code, success, code, message):
# 调用 自己封装的 api, 获取响应结果
resp = LoginApi.login(req_data)
# 打印查看
# print(desc, ":", resp.json())
logging.info(f"{desc} : {resp.json()}")
# 断言
common_assert(resp, status_code, success, code, message)
6.注意
(1)解决数据反复修改问题
- 数据使用一次后不能再次使用,比如手机号、用户id,在测试前(setup),要保证数据库里没有该数据,所以可执行delete SQL语句,将其删除!
- 在测试 添加接口结束之后(teardown),删除添加接口测试时,使用的手机号或者用户id。
- sql语句 手机号 固定常量 可重复使用!将手机号设置 全局变量。
# 定义全局变量,测试用的手机号
TEL = "18648390001"
from config import TEL
def setup(self):
del_sql = f"delete from bs_user where mobile='{TEL}';"
DBTools.db_uid(del_sql)
def teardown(self):
del_sql = f"delete from bs_user where mobile='{TEL}';"
DBTools.db_uid(del_sql)
# 定义测试方法 - 添加成功
def test01_add_ok(self):
# 调用自己封装的 api,发送 添加员工请求
req_data = {
"username": "user87655430",
"mobile": TEL,#定义TEL本身就是一个字符串,不用使用格式占位符
"workNumber": "9527890aa"
}
resp = EmpApi.add_emp(self.req_header, req_data)
print("必选-添加成功:", resp.json())
# 断言
common_assert(resp, 200, True, 10000, "操作成功")
(2)用例自动化中,存在部分用例无法执行(手机号重复):
为了解决手机号重复问题,我们将手机号设置为全局变量,如果要执行手机号重复的自动化用例,和我们设置的全局变量冲突,这种情况,可以手动补充该种场景去执行!
7.请求头动态获取
请求头需要动态获取,每次调用接口之前,都需要获取新的请求头,所以 抽出来写成一个方法,在类执行之前调用一次(setup_class)
"""
定义 函数,获取登录成功令牌,组织请求头
"""
from api.login_api import LoginApi
def get_header():
req_data = {"mobile": "13800000002", "password": "123456"}
resp = LoginApi.login(req_data)
header = {"Authorization": resp.json().get("data")}
return header
if __name__ == '__main__':
ret = get_header()
print(ret)
# 定义测试类 - 实现参数化
class TestAddEmpParams(object):
req_header = None
def setup_class(self):
self.req_header = get_header()
8.生成测试报告
[pytest]
addopts = -s --html=./report/Report.html --self-contained-html
testpaths = ./scripts
python_files = test*.py
python_classes = Test*
python_functions = test*
addopts:pytest执行时携带的参数 --html=./report/Report.html 报告存放路径(./代表当前ini文件的同级目录)
参数–self-containted-html:测试用例执行过程中会生成一些中间缓存文件,加该参数 不需要生成该中间文件
9.日志收集
(1).日志:记录系统运行过程中产生的信息,对一个事件的记录,也成log。
有哪些信息需要记录:
- 脚本运行过程中某个重要变量的值
- 方法的输入参数和返回结果
- 异常信息
(2).日志级别:
logging.DEBUG:调试级别,级别高
logging.info:信息级别,次高
logging.warning:警告级别 中
logging.error:错误级别,低
logging.critical:严重错误级别 极低
(3).定义自动化脚本运行的日志级别:一般调试或者信息级别用的较多(设置比设置日志级别低的日志才会打印)
import logging.handlers
import logging
from config import BASE_DIR
def init_log_config(filename, when='midnight', interval=1, backup_count=7):
"""
功能:初始化日志配置函数
:param filename: 日志文件名
:param when: 设定日志切分的间隔时间单位,midnight,代表午夜
:param interval: 间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
:param backup_count: 保留日志文件的个数,7代表保留近七天
:return:
"""
# 1. 创建日志器对象
logger = logging.getLogger()
# 2. 设置日志打印级别
logger.setLevel(logging.DEBUG)
# logging.DEBUG 调试级别
# logging.INFO 信息级别
# logging.WARNING 警告级别
# logging.ERROR 错误级别
# logging.CRITICAL 严重错误级别
# 3. 创建处理器对象
# 控制台对象
st = logging.StreamHandler()
# 日志文件对象
fh = logging.handlers.TimedRotatingFileHandler(filename,
when=when,
interval=interval,
backupCount=backup_count,
encoding='utf-8')
# 4. 日志信息格式
fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
formatter = logging.Formatter(fmt)
# 5. 给处理器设置日志信息格式
st.setFormatter(formatter)
fh.setFormatter(formatter)
# 6. 给日志器添加处理器
logger.addHandler(st)
logger.addHandler(fh)
if __name__ == '__main__':
# 初始化日志
init_log_config(BASE_DIR + '/log/sh27.log', interval=3, backup_count=5)
# 打印输出日志信息
logging.debug('我是一个调试级别的日志')
logging.info("AAA BBB CCC")
logging.error("xxxx 错误。。")
class TestLoginParams(object):
init_log_config(BASE_DIR + '/log/sh27.log', interval=3, backup_count=5)
三、完整的脚本实现
api目录:
import requests
# 定义 员工管理 类
class EmpApi(object):
# 定义 添加员工 方法
@classmethod
def add_emp(cls, header, req_data):
resp = requests.post(url="http://ihrm-test.itheima.net/api/sys/user",
headers=header, json=req_data)
return resp
# 定义 查询员工 方法
@classmethod
def query_emp(cls, emp_id, header):
resp = requests.get(url="http://ihrm-test.itheima.net/api/sys/user/"+emp_id, headers=header)
return resp
# 定义 修改员工 方法
@classmethod
def modify_emp(cls, emp_id, header, req_data):
resp = requests.put(url="http://ihrm-test.itheima.net/api/sys/user/"+emp_id,
headers=header, json=req_data)
return resp
# 定义 删除员工 方法
@classmethod
def del_emp(cls, emp_id, header):
return requests.delete(url="http://ihrm-test.itheima.net/api/sys/user/"+emp_id, headers=header)
if __name__ == '__main__':
req_header = {"Authorization": "ca0da927-ae56-4902-b8ce-78ab8c8b55ad"}
json = {
"username": "user87655430",
"mobile": "13947874780",
"workNumber": "9527890aa"
}
response = EmpApi.add_emp(req_header, json)
print("添加员工结果:", response.json())
response = EmpApi.query_emp("1494919461403222016", req_header)
print("查询员工结果:", response.json())
modify_data = {"username": "放大镜考虑"}
response = EmpApi.modify_emp("1494919461403222016", req_header, modify_data)
print("修改员工结果:", response.json())
response = EmpApi.del_emp("1494919461403222016", req_header)
print("删除员工结果:", response.json())
scripts目录:
common目录:
data目录:
report目录:
pytest.ini:
[pytest]
addopts = -s --html=./report/ihrmReport.html --self-contained-html
testpaths = ./scripts
python_files = test*.py
python_classes = Test*
python_functions = test*
config.py: