前面写过的框架,只能说是个基本框架,不能满足项目需求,接下来通过项目实战,来完善项目框架。
一.以一个简单注册接口为例,接口文档如下:
二.在开始前需要根据接口文档编写测试用例,这里推荐用excel来管理测试数据,简单的写了几条如下:(手机号先写成固定的手机号,后面在做优化)
三.前面搭建基础框架的时候简单介绍过,这里就不一一详细介绍了。
四.先来看注册的用例代码,在写之前先理一下大概流程:
1.数据在excel,所以需要导入excel方法来读取数据
2.读取测试数据后,需要导入ddt做数据驱动
3.然后发起http请求,需要导入封装好的request方法,根据实际结果与预期结果做对比
4.代码执行完成需要记录日志,需要导入日志方法
5.log的信息放在了yaml文件中,需要导入读取yaml文件的方法
6.生成日志,读取数据等需要用到路径,需要导入路径文件
前面已经封装好的方法就不一一贴出来了,request方法前面没写,如下:
import requests
def visit(url,method='get',params=None,data=None,json=None,**kwargs):
res=requests.request(method,url,params=params,data=data,json=json,**kwargs)
try:
return res.json()
except Exception as e:
print("格式错误{}".format(e))
import unittest
import ddt
import json
import os
from common import handler_request#导入封装好的requests类
from common import handler_excel#导入excel方法
from common import logger_handler#导入日志方法
from common import path_config#导入所有文件的路径
from common import read_yaml#导入读取yaml文件的方法
case_path=os.path.join(path_config.DATA_PATH,'case.xlsx')#用例数据的路径
excel=handler_excel.ExcelMothed(case_path)#初始化
register_cases=excel.get_case('register')#获取注册的case
log_info=read_yaml.read_yaml(os.path.join(path_config.CONFIG_PATH,'config.yml'))['log']#获得yaml中的log信息
logger=logger_handler.get_logger('register',logger_level=log_info['logger_level'],stream_level=log_info['stream_level'],handler_level=log_info['file_level'])
@ddt.ddt
class TestRegister(unittest.TestCase):
@ddt.data(*register_cases)
def testregister(self,case):
res=handler_request.visit(case['url'],case['method'],json=json.loads(case['data']),headers=json.loads(case['headers']))#请求接口
try:
self.assertTrue(json.loads(case['expected'])['code']==res['code'])#断言实际结果与预期结果
self.assertTrue(json.loads(case['expected'])['msg']==res['msg'])
except AssertionError as e:
logger.error('第{}条用例失败'.format(case['case_id']))
else:
logger.info("第{}条用例通过".format(case['case_id']))
运行看一下结果:
自动化测试就实现了,但是如果测试数据没有改变而再次运行的话,注册成功的用例就会失败,所以就会造成每次测试前需要去手动改用例中的测试数据(手机号),为避免这种情况可以通过数据参数化来保证每次测试的时候数据不一样。
基本思路:每次都随机生成手机号,并替换掉excel中的手机号。
代码如下:此方法也放到了common中
import random
def random_phone():
'''随机生成手机号'''
while True:
phone = '1' + random.choice(['5', '3']) # 随机以13或15开头的号段
for i in range(9):
num = random.randint(0, 9)
phone += str(num)
return phone
手机号现在有了,但是如何去与excel中的数据做替换呢?
可以通过在excel中把需要替换的值,设计成一个变量值,每次与需要的变量替换就行,如下:
代码实现,每次判断data中是否存在@mobile,如果存在则替换成随机生成的手机号。
代码如下:
import unittest
import ddt
import json
import os
from common import handler_request#导入封装好的requests类
from common import handler_excel#导入excel方法
from common import logger_handler#导入日志方法
from common import path_config#导入所有文件的路径
from common import read_yaml#导入读取yaml文件的方法
case_path=os.path.join(path_config.DATA_PATH,'case.xlsx')#用例数据的路径
excel=handler_excel.ExcelMothed(case_path)#初始化
register_cases=excel.get_case('register')#获取注册的case
log_info=read_yaml.read_yaml(os.path.join(path_config.CONFIG_PATH,'config.yml'))['log']#获得yaml中的log信息
logger=logger_handler.get_logger('register',logger_level=log_info['logger_level'],stream_level=log_info['stream_level'],handler_level=log_info['file_level'])
@ddt.ddt
class TestRegister(unittest.TestCase):
@ddt.data(*register_cases)
def testregister(self,case):
if '@mobile' in case['data']:#判断excel的data中是否存在'@mobile'字符串
phone=random_phone()#调用生成手机号
case['data']=case['data'].replace('@mobile',phone)#将'@mobile' 替换成随机生成的手机号
res=handler_request.visit(case['url'],case['method'],json=json.loads(case['data']),headers=json.loads(case['headers']))#请求接口
try:
self.assertTrue(json.loads(case['expected'])['code']==res['code'])#断言实际结果与预期结果
self.assertTrue(json.loads(case['expected'])['msg']==res['msg'])
except AssertionError as e:
logger.error('第{}条用例失败'.format(case['case_id']))
else:
logger.info("第{}条用例通过".format(case['case_id']))
到现在为止基本上完成了一个接口自动化测试流程,但是就目前这个注册接口来说还是不够严谨的。
1.不敢保证每次生成的手机号都没有被注册过
2.注册成功的手机号,一定注册成功了吗?数据库中一定存在吗?
所以在生成手机号后需要查询数据库有没有存在,注册成功后需要查询数据库有没有存在。
封装数据库走起:数据库配置信息放在了yaml配置文件中
import pymysql
from pymysql.cursors import DictCursor
class MysqlHandler:
def __init__(self,host=None,user=None,password=None,port=3306,charset='utf8',cursorclass=DictCursor):
self.conn=pymysql.connect(host=host,user=user,password=password,port=port,charset='utf8',cursorclass=DictCursor)
self.cursor = self.conn.cursor() # 实例化游标
def query(self,sql,one=True):
self.cursor.execute(sql)
if one:
return self.cursor.fetchone()
return self.cursor.fetchall()
def mysql_close(self):
self.conn.close()
self.cursor.close()
完善生成手机号的代码:
import os
import random
from common import handler_mysql
from common.read_yaml import read_yaml
from common import path_config
sql_info=read_yaml(os.path.join(path_config.CONFIG_PATH,'config.yml'))['mysqlinfo']#获取数据库配置信息
db=handler_mysql.MysqlHandler(host=sql_info['host'],user=sql_info['user'],password=sql_info['password'])#
def random_phone():
'''随机生成手机号'''
while True:
phone = '1' + random.choice(['5', '3']) # 随机以13或15开头的号段
for i in range(9):
num = random.randint(0, 9)
phone += str(num)
if not db.query('SELECT mobile_phone FROM member '
'where mobile_phone={}'.format(phone)): # 查询生成的手机号是否已存在
return phone # 不存在返回手机号,存在则继续循环生成
db.cursor.close() # 关闭游标
db.mysql_close() # 关闭数据库
if __name__=='__main__':
print(random_phone())
注册成功后也需要去与数据库对比,判断注册成功可以通过响应结果的code==0做为依据,查询数据库是否存在并断言。代码如下:
import unittest
import ddt
import json
import os
from common import handler_request#导入封装好的requests类
from common import handler_excel
from common import logger_handler
from common.random_phone import random_phone#导入封装的随机生成手机号
from common import path_config
case_path=os.path.join(path_config.DATA_PATH,'case.xlsx')
excel=handler_excel.ExcelMothed(case_path)
from common import handler_mysql
from common.read_yaml import read_yaml
from common import path_config
sql_info=read_yaml(os.path.join(path_config.CONFIG_PATH,'config.yml'))['mysqlinfo']
db=handler_mysql.MysqlHandler(host=sql_info['host'],user=sql_info['user'],password=sql_info['password'])
register_cases=excel.get_case('register')#获取注册的case
log_info=read_yaml(os.path.join(path_config.CONFIG_PATH,'config.yml'))['log']
logger=logger_handler.get_logger('register',logger_level=log_info['logger_level'],
stream_level=log_info['stream_level'],
handler_level=log_info['file_level'])
@ddt.ddt
class TestRegister(unittest.TestCase):
@ddt.data(*register_cases)
def testregister(self,case):
if '@mobile' in case['data']:#判断excel的data中是否存在'@mobile'字符串
phone=random_phone()#调用生成手机号
case['data']=case['data'].replace('@mobile',phone)#将'@mobile' 替换成随机生成的手机号
data=json.loads(case['data'])#转换成data格式
res=handler_request.visit(case['url'],case['method'],json=json.loads(case['data']),headers=json.loads(case['headers']))#请求接口
try:
self.assertTrue(json.loads(case['expected'])['code']==res['code'])
self.assertTrue(json.loads(case['expected'])['msg']==res['msg'])
if res['code']==0:# 注册成功的话会查寻数据库手机号是否存在,并断言
sql_result=db.query('SELECT mobile_phone FROM member where mobile_phone={}'.format(data['mobile_phone']))
db.mysql_close()
db.cursor.close()
self.assertTrue(sql_result)
except AssertionError as e:
logger.error('第{}条用例失败'.format(case['case_id']))
raise e
else:
logger.info("第{}条用例通过".format(case['case_id']))
测试一个接口难道需要这么一大坨代码?,冗余的地方太多了,就比如数据库这个方法,在生成手机号的时候需要调用,从yaml读数据,实例化,然后再注册成功的时候又需要调用,从yaml读数据,实例化。。。。如果有很多的测试用例,每次都去这样调用?还有日志方法等等…
可以通过二次封装的办法,把这些方法,配置项或路径再次封装起来,单独放到一个文件下:middleware中间层,
当然可以随便起名名字;
新建一个py文件为handler,可以再里面定义一个类为Handler,像文件路径,yaml配置文件的内容,excel初始化,日志的初始化,都可以再这个类中完成,哪里需要哪里调用不香吗?
handler中的代码如下:
import os
from common import read_yaml
from common import path_config
from common import handler_excel
from common import logger_handler
from common.handler_mysql import MysqlHandler
from pymysql.cursors import DictCursor
class Handler:
root_path=path_config#文件路径
yaml_info=read_yaml.read_yaml(os.path.join(root_path.CONFIG_PATH,'config.yml'))#配置文件内容
###excel
#这里的__表示此类的私有属性,外部不可以调用
__case_name = yaml_info['excel']['file_name']# 获取excel用例的名称
__casepath = os.path.join(root_path.DATA_PATH,__case_name)# 获取excel文件的路径
excel= handler_excel.ExcelMothed(__casepath)# 初始化
#####日志
def logger(self,name):
__log_path = os.path.join(path_config.LOG_PATH, 'logs')
__log_info = Handler.yaml_info['log']
Logger = logger_handler.get_logger(name,__log_path,
logger_level= __log_info['logger_level'],
stream_level= __log_info['stream_level'],
handler_level= __log_info['file_level'])
return Logger
class MidMysqlHandler(MysqlHandler):#继承封装好的MysqlHandler
def __init__(self):
mysqlinfo = Handler.yaml_info['mysqlinfo']
super().__init__(host=mysqlinfo['host'],
user=mysqlinfo['user'],
password=mysqlinfo['password'],
port=mysqlinfo['port'],charset='utf8',cursorclass=DictCurs
最后来看下注册的用例代码。
如下:简洁了很多。。所有的读取配置项,初始化操作都已经在handler中完成了。
import unittest
import ddt
import json
from common import handler_request#导入封装好的requests类
from middleware.handler import Handler,MidMysqlHandler#导入handler中的两个类
from common.random_phone import random_phone#导入封装的随机生成手机号
register_cases=Handler().excel.get_case('register')#获取注册的case
logger=Handler().logger('register')
@ddt.ddt
class TestRegister(unittest.TestCase):
@ddt.data(*register_cases)
def testregister(self,case):
if '@mobile' in case['data']:#判断excel的data中是否存在'@mobile'字符串
phone=random_phone()#调用生成手机号
case['data']=case['data'].replace('@mobile',phone)#将'@mobile' 替换成随机生成的手机号
data=json.loads(case['data'])#转换成data格式
res=handler_request.visit(case['url'],case['method'],json=json.loads(case['data']),headers=json.loads(case['headers']))#请求接口
try:
self.assertTrue(json.loads(case['expected'])['code']==res['code'])
self.assertTrue(json.loads(case['expected'])['msg']==res['msg'])
if res['code']==0:# 注册成功的话会查寻数据库手机号是否存在,并断言
db = MidMysqlHandler()
sql_result=db.query('SELECT mobile_phone FROM futureloan.member where mobile_phone={}'.format(data['mobile_phone']))
db.mysql_close()
db.cursor.close()
self.assertTrue(sql_result)
except AssertionError as e:
logger.error('第{}条用例失败'.format(case['case_id']))
raise e
else:
logger.info("第{}条用例通过".format(case['case_id']))