测试用例基类抽取
1. 抽取思路
1.1 将公用模块都封装到基类中便于子类直接调用
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/11 11:12
# @Author : shisuiyi
# @File : base_case.py
# @Software: win10 Tensorflow1.13.1 python3.9
import unittest
import setting
from common import logger, db
class BaseCase(unittest.TestCase):
"""
用例基类
"""
db = db
logger = logger
setting = setting
1.2 将测试方法中的每一个步骤单独封装成对象方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/2 21:28
# @Author : shisuiyi
# @File : test_audit.py
# @Software: win10 Tensorflow1.13.1 python3.9
import json
from unittestreport import ddt, list_data
from common.data_handler import (
get_data_from_excel,
generate_no_usr_phone,
replace_args_by_re)
from common.fixture import register, login, add_loan
from common.make_requests import send_http_request
from common.basecase import BaseCase
cases = get_data_from_excel(BaseCase.setting.TEST_DATA_FILE, 'audit')
@ddt
class TestAudit(BaseCase):
@classmethod
def setUpClass(cls) -> None:
cls.logger.info('===========项目审核接口开始测试===========')
# 1.注册借钱用户
mobile_phone = generate_no_usr_phone()
pwd = '12345678'
register(mobile_phone, pwd)
# 2.登录借钱用户
data = login(mobile_phone, pwd)
# 保存投资用户的数据用来创建标,保存在类属性中
# 要保存借钱用户的id和token
cls.normal_member_id = data['id']
cls.normal_token = data['token_info']['token']
# 3.注册管理员用户
mobile_phone = generate_no_usr_phone()
register(mobile_phone, pwd, _type=0)
# 4.登录管理员用户
data = login(mobile_phone, pwd)
# 保存管理员用户的token
cls.token = data['token_info']['token']
@classmethod
def tearDownClass(cls) -> None:
cls.logger.info('===========项目审核接口结束测试===========')
def setUp(self) -> None:
"""
方法级前置
:return:
"""
# 创建项目
res = add_loan(member_id=self.__class__.normal_member_id,
token=self.__class__.normal_token)
# 将创建好的项目的id传递到测试用例中
# 通过对象属性
self.loan_id = res['id']
@list_data(cases)
def test_audit(self, item):
"""
作业
:param item:
:return:
"""
self.logger.info('>>>>>>>用例{}开始执行>>>>>>>>'.format(item['title']))
# 把测试数据绑定到方法属性case上,其他也要把一些变量绑定到对象的属性上
self._case = item
# 1. 处理测试数据
self.process_test()
# 2. 发送请求
self.send_request()
# 3. 断言
self.assert_all()
self.logger.info('---------------用例{}测试成功---------------'.format(self._case['title']))
def process_test(self):
"""
测试数据的处理
"""
# 需要替换依赖参数
self._case = json.dumps(self._case) # 把用例数据dumps成字符串,一次替换
self._case = replace_args_by_re(self._case, self)
# item = item.replace('#loan_id#', str(self.loan_id))
# item = item.replace('#token#', self.__class__.token)
self._case = json.loads(self._case)
# 再将request_data, expect_data loads为字典
self._case['request_data'] = json.loads(self._case['request_data'])
self._case['expect_data'] = json.loads(self._case['expect_data'])
# 处理url
if self._case['url'].startswith('http'):
# 是否是全地址
pass
elif self._case['url'].startswith('/'):
# 是否是短地址
self._case['url'] = self.setting.PROJECT_HOST + self._case['url']
else:
# 接口名称
self._case['url'] = self.setting.INTERFACES[self._case['url']]
def send_request(self):
"""
发送请求
@return:
"""
self.response = send_http_request(url=self._case['url'], method=self._case['method'],
**self._case['request_data'])
def assert_all(self):
# 3.1 断言响应状态码
try:
self.assertEqual(self._case['status_code'], self.response.status_code)
except AssertionError as e:
self.logger.warning('用例【{}】响应状态码断言异常'.format(self._case['title']))
self.logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(self._case['title']))
raise e
else:
self.logger.info('用例【{}】响应状态码断言成功'.format(self._case['title']))
# 3.2 断言响应数据
if self._case['res_type'].lower() == 'json':
res = self.response.json()
elif self._case['res_type'].lower() == 'html':
# 扩展思路
res = self.response.text
try:
self.assertEqual(self._case['expect_data'], {'code': res['code'], 'msg': res['msg']})
except AssertionError as e:
self.logger.warning('用例【{}】响应数据断言异常'.format(self._case['title']))
self.logger.warning('用例【{}】期望结果为:{}'.format(self._case['title'], self._case['expect_data']))
self.logger.warning('用例【{}】的响应结果:{}'.format(self._case['title'], res))
self.logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(self._case['title']))
raise e
else:
self.logger.info('用例【{}】响应数据断言成功'.format(self._case['title']))
# 3.3 数据库断言后面的任务
if self._case.get('sql'): # 返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。
# 只有sql字段有sql的才需要校验数据库
try:
self.assertTrue(self.db.exist(self._case['sql']))
except AssertionError as e:
self.logger.warning('用例【{}】数据库断言异常,执行的sql为:{}'.format(self._case['title'], item['sql']))
self.logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(self._case['title']))
raise e
if __name__ == '__main__':
BaseCase.unittest.main()
1.3 按照第二步的思想,进一步将小步骤抽成更小的功能方法
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/11 11:12
# @Author : shisuiyi
# @File : base_case.py
# @Software: win10 Tensorflow1.13.1 python3.9
import json
import unittest
import setting
from common import logger, db
from common.data_handler import (
replace_args_by_re,
generate_no_usr_phone)
from common.make_requests import send_http_request
class BaseCase(unittest.TestCase):
"""
用例基类
"""
db = db
logger = logger
setting = setting
name = 'base_case'
@classmethod
def setUpClass(cls) -> None:
cls.logger.info('==========={}接口开始测试==========='.format(cls.name))
@classmethod
def tearDownClass(cls) -> None:
cls.logger.info('==========={}接口结束测试==========='.format(cls.name))
def flow(self, item):
"""
测试流程
"""
self.logger.info('>>>>>>>用例{}开始执行>>>>>>>>'.format(item['title']))
# 把测试数据绑定到方法属性case上,其他也要把一些变量绑定到对象的属性上
self._case = item
# 1. 处理测试数据
self.process_test()
# 2. 发送请求
self.send_request()
# 3. 断言
self.assert_all()
def process_test(self):
"""
测试数据的处理
"""
# 1.1 生成测试数据
self.generate_test_data()
# 1.2 替换依赖参数
self.replace_args()
# 1.3 处理url
self.process_url()
def generate_test_data(self):
"""
生成测试数据
"""
"""
生成测试数据,不是固定流程,有不同可以复写
:return:
"""
self._case = json.dumps(self._case)
if '$phone_number$' in self._case:
phone = generate_no_usr_phone()
self._case = self._case.replace('$phone_number$', phone)
self._case = json.loads(self._case)
def replace_args(self):
"""
替换参数
"""
self._case = json.dumps(self._case) # 把用例数据dumps成字符串,一次替换
self._case = replace_args_by_re(self._case, self)
self._case = json.loads(self._case)
# 再将request_data, expect_data loads为字典
try:
self._case['request_data'] = json.loads(self._case['request_data'])
self._case['expect_data'] = json.loads(self._case['expect_data'])
except Exception as e:
self.logger.error('{}用例的测试数据格式不正确'.format(self._case['title']))
raise e
def process_url(self):
"""
处理url
"""
if self._case['url'].startswith('http'):
# 是否是全地址
pass
elif self._case['url'].startswith('/'):
# 是否是短地址
self._case['url'] = self.setting.PROJECT_HOST + self._case['url']
else:
# 接口名称
try:
self._case['url'] = self.setting.INTERFACES[self._case['url']]
except Exception as e:
self.logger.error('接口名称:{}不存在'.format(self._case['url']))
raise e
def send_request(self):
"""
发送请求
@return:
"""
self._response = send_http_request(url=self._case['url'], method=self._case['method'],
**self._case['request_data'])
def assert_all(self):
try:
# 3.1 断言响应状态码
self.assert_status_code()
# 3.2 断言响应数据
self.assert_response()
# 3.3 数据库断言后面的任务
self.assert_sql()
except Exception as e:
self.logger.error('++++++用例{}测试失败'.format(self._case['title']))
raise e
else:
self.logger.info('<<<<<<<<<用例{}测试成功<<<<<<<'.format(self._case['title']))
def assert_status_code(self):
"""
断言响应状态码
"""
try:
self.assertEqual(self._case['status_code'], self._response.status_code)
except AssertionError as e:
self.logger.warning('用例【{}】响应状态码断言异常'.format(self._case['title']))
raise e
else:
self.logger.info('用例【{}】响应状态码断言成功'.format(self._case['title']))
def assert_response(self):
"""
断言响应数据
"""
if self._case['res_type'].lower() == 'json':
res = self._response.json()
elif self._case['res_type'].lower() == 'html':
# 扩展思路
res = self._response.text
try:
self.assertEqual(self._case['expect_data'], {'code': res['code'], 'msg': res['msg']})
except AssertionError as e:
self.logger.warning('用例【{}】响应数据断言异常'.format(self._case['title']))
self.logger.warning('用例【{}】期望结果为:{}'.format(self._case['title'], self._case['expect_data']))
self.logger.warning('用例【{}】的响应结果:{}'.format(self._case['title'], res))
raise e
else:
self.logger.info('用例【{}】响应数据断言成功'.format(self._case['title']))
def assert_sql(self):
"""
断言数据库
"""
if self._case.get('sql'): # 返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。
# 只有sql字段有sql的才需要校验数据库
try:
self.assertTrue(self.db.exist(self._case['sql']))
except AssertionError as e:
self.logger.warning('用例【{}】数据库断言异常,执行的sql为:{}'.format(self._case['title'], self._case['sql']))
raise e
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/12/2 21:28
# @Author : shisuiyi
# @File : test_audit.py
# @Software: win10 Tensorflow1.13.1 python3.9
from unittestreport import ddt, list_data
from common.fixture import register, login, add_loan
from common.make_requests import send_http_request
from common.data_handler import (
get_data_from_excel,
generate_no_usr_phone)
from common.base_case import BaseCase
cases = get_data_from_excel(BaseCase.setting.TEST_DATA_FILE, 'audit')
@ddt
class TestAudit(BaseCase):
name = '项目审核'
@classmethod
def setUpClass(cls) -> None:
# 当子类重写了父类方法时,又想调用父类的同名方法时,就需要用到 super()
super().setUpClass()
# 1.注册借钱用户
mobile_phone = generate_no_usr_phone()
pwd = '12345678'
register(mobile_phone, pwd)
# 2.登录借钱用户
data = login(mobile_phone, pwd)
# 保存投资用户的数据用来创建标,保存在类属性中
# 要保存借钱用户的id和token
cls.normal_member_id = data['id']
cls.normal_token = data['token_info']['token']
# 3.注册管理员用户
mobile_phone = generate_no_usr_phone()
register(mobile_phone, pwd, _type=0)
# 4.登录管理员用户
data = login(mobile_phone, pwd)
# 保存管理员用户的token
cls.token = data['token_info']['token']
def setUp(self) -> None:
"""
方法级前置
:return:
"""
# 创建项目
res = add_loan(member_id=self.__class__.normal_member_id,
token=self.__class__.normal_token)
# 将创建好的项目的id传递到测试用例中
# 通过对象属性
self.loan_id = res['id']
@list_data(cases)
def test_audit(self, item):
"""
:param item:
:return:
"""
self.flow(item)
if __name__ == '__main__':
BaseCase.unittest.main()