现在企业对于软件测试的人员的要求越来越高,自动化已经是中级软件测试工程师的基本职业技能之一,那么我们今天来搭建一个接口自动化框架玩玩
既然是框架,那么就得有结构设计理念,我们这用的是分层分包的方式,实现高内聚低耦合的面向对象的一种编程方式。
我这里用的是unittest+requests+htmltestrunner+ddt搭建的框架
先把各个不同功能的文件夹建好:
1.common包放公共的脚本,比如连接数据库的、写日志的、发请求的、还要其他什么读数据的啊、拿token的啊、读配置文件的往里面扔就完事了
2.conf包呢就主要是放配置文件,比如测试环境、SSH信息、数据库信息等,方便维护以及参数化
3.data包用来放测试数据,我这里用的是excel,之前也用过yml
4.testCase包呢,顾名思义,就是用来放测试用例的包啦
5.testLogs用来放日志文件,每次脚本运行都会生成日志文件,就统一放在这个文件夹内
6.testReport包就来放htmltestrunner生成的测试报告
7.最后留一个run方法,用来作为框架运行的入口
包建好了之后呢,就来开始写脚本了。
我先写的是连接数据库的脚本,因为之前没有用ssh连过mysql,这次想试一试,然后一顿操作呢,没有成功,在网上找了个脚本,自己改吧改吧,也就成了,我的ip和端口号那些都用了传参的方式,所以如果有大佬要用的话,就麻烦改一改,别到时候来骂我:
import pymysql
from sshtunnel import SSHTunnelForwarder
from API.common.readConfig import ReadConfig
class DataBase:
def __init__(self):
read = ReadConfig()
self.server = SSHTunnelForwarder(
read.get_config_str("ssh","sshHost"), read.get_config_int("ssh","sshPort"), #ssh IP和port
ssh_password = read.get_config_str("ssh","sshPassword"), #ssh 密码
ssh_username = read.get_config_str("ssh","sshUser"), #ssh账号
remote_bind_address = (read.get_config_str("mysql","dbHost"), read.get_config_int("mysql","dbPort")))#数据库所在的IP和端口
#启动服务
self.server.start()
#打印本地端口,已检查是否配置正确
print(self.server.local_bind_host)
def Get_db(self,sql):
read = ReadConfig()
self.goDB = pymysql.connect(host = read.get_config_str("mysql","dbHost"), #固定写法
port = self.server.local_bind_port,
user = read.get_config_str("mysql","dbUser"), #数据库账号
passwd = read.get_config_str("mysql","dbPassword"), #数据库密码
db = read.get_config_str("mysql","dbName")) #可以限定,只访问特定的数据库,否则需要在mysql的查询或者操作语句中,指定好表名
cur = self.goDB.cursor()
try:
#执行SQL语句检查是否连接成功
cur.execute("select version()")
result = cur.fetchone()
print("Database version: %s" % result)
cur.execute(sql)
data = cur.fetchall()
return data
except:
print("Error")
#关闭连接
def __del__(self):
self.goDB.close()
self.server.close()
然后是封装日志类的文件:
import logging
import time
class GetLog:
def __init__(self):
curTime = time.strftime('%Y-%m-%d')
self.logname = 'testLogs/AutoTest' + '_' + curTime + '.txt'
def get_log(self,level,msg):
# 创建日志收集器
logger = logging.getLogger()
logger.setLevel('DEBUG')
# 创建handler
fh = logging.FileHandler(self.logname,'a',encoding='gbk')
fh.setLevel('INFO')
ch = logging.StreamHandler()
ch.setLevel('INFO')
# 定义handler的输出格式
formatter = logging.Formatter('%(asctime)s - %(filename)s - %(name)s - %(levelname)s - 日志信息: %(message)s')
ch.setFormatter(formatter)
fh.setFormatter(formatter)
# 给logger添加handler
logger.addHandler(fh)
logger.addHandler(ch)
if level == 'DEBUG':
logger.debug(msg)
elif level == 'INFO':
logger.info(msg)
elif level == 'WARNING':
logger.warning(msg)
elif level == 'ERROR':
logger.error(msg)
elif level == 'CRITICAL':
logger.critical(msg)
logger.removeHandler(fh)
logger.removeHandler(ch)
fh.close()
def log_debug(self,msg):
self.get_log('DEBUG',msg)
def log_info(self,msg):
self.get_log('INFO',msg)
def log_warning(self,msg):
self.get_log('WARNING',msg)
def log_error(self,msg):
self.get_log('ERROR',msg)
def log_critical(self,msg):
self.get_log('CRITICAL',msg)
接着封装发请求的类:
import requests
class HttpRequest:
def __init__(self,method,url,data=None,token=None,headers=None):
try:
if method == 'GET':
self.response = requests.get(url=url,params=data,headers=headers)
elif method == 'POST':
self.response = requests.post(url=url,data=data,headers=headers)
elif method == 'detele':
self.response = requests.delete(url=url,data=data,headers=headers)
except Exception as e:
raise e
def get_status_code(self):
return self.response.status_code
def get_text(self):
return self.response.text
def get_json(self):
return self.response.json()
读写excel的类:
import openpyxl
class Case:
def __init__(self):
self.case_id = None
self.url = None
self.data = None
self.title = None
self.method = None
self.expected = None
class ReadExcel:
def __init__(self,file_name):
try:
self.filename = file_name
self.wb = openpyxl.load_workbook(self.filename)
except FileNotFoundError as e:
print('{0} not found , please check file path'.format(self.filename))
raise e
def get_cases(self,sheet_name):
sheet = self.wb[sheet_name]
max_row = sheet.max_row
test_cases = []
for r in range(2,max_row+1):
case = Case() # 实例化一个data对象,用于存放读取的测试数据
case.case_id = sheet.cell(row=r,column=1).value
case.title = sheet.cell(row=r,column=2).value
case.method = sheet.cell(row=r,column=3).value
case.url = sheet.cell(row=r,column=4).value
case.data = sheet.cell(row=r,column=5).value
case.expected = sheet.cell(row=r,column=6).value
case.header = sheet.cell(row=r,column=7).value
test_cases.append(case)
return test_cases
# 获取到workbook里面所有的sheet名称的列表
def get_sheet_name(self):
return self.wb.sheetnames
# 根据sheet_name定位到sheet,根据case_id定位到行,写入result
def write_result(self,sheet_name,case_id,actual,result):
sheet = self.wb[sheet_name]
max_row = sheet.max_row
for r in range(2,max_row+1):
# 获取第r行第1列的 case_id值
case_id_r = sheet.cell(row=r,column=1).value
# 判断excel读取的当前行的case_id_r是否与传入的case_id相同
if case_id_r == case_id:
sheet.cell(row=r,column=7).value = actual
sheet.cell(row=r,column=8).value = result
self.wb.save(filename=self.filename)
break
读配置文件的类:
import configparser
class ReadConfig:
# 初始化函数:实例化conf对象,读取传入的配置文件
def __init__(self):
self.conf = configparser.ConfigParser()
self.conf.read("conf/config.ini")
def get_config_str(self,section,option):
return self.conf.get(section,option)
def get_config_boolean(self,section,option):
return self.conf.getboolean(section,option)
def get_config_int(self,section,option):
return self.conf.getint(section,option)
def get_config_float(self,section,option):
return self.conf.getfloat(section,option)
def get_items(self,section):
return self.conf.items(section)
到这一步呢,common包内的方法就写的差不多了,我因为被测系统设计的原因,多加了一个get_token的类,这儿就不写出来了。
然后是run方法:
import unittest
from HTMLTestRunner import HTMLTestRunner
import time
from API.testCase.test_1 import TestRecharge
from API.common.getlog import GetLog
get_log = GetLog()
def RunTest():
suite = unittest.TestSuite()
loader = unittest.TestLoader()
suite.addTest(loader.loadTestsFromTestCase(TestRecharge))
cur_time = time.strftime('%Y-%m-%d_%H_%M_%S')
report_name = 'testReport/test_results' + cur_time + '.html'
with open(report_name,'wb+') as file:
runner = HTMLTestRunner.HTMLTestRunner(stream=file,
verbosity=2,
title='接口测试报告',
description='基于python+unittest进行的数据驱动接口自动化测试',
)
runner.run(suite)
if __name__ == '__main__':
get_log.log_info('「ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ Api Request AutoTest Start ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ」 ')
RunTest()
get_log.log_info('「ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ Api Request AutoTest End ˉˉˉˉˉˉˉˉˉˉˉˉˉˉˉ」 ')
写到这里,最后一步就是test_case的脚本开发了,调用之前的公共方法,在这执行excel一页sheet的测试用例,然后用run方法调用testcase就可以了,这一步呢,需要结合项目实际情况去开发脚本:
from API.common.readConfig import ReadConfig
from API.common.dataBase import DataBase
from API.common.http_request import HttpRequest
from API.common.oper_token import Get_token
from API.common.read_excel import ReadExcel
from API.common.getlog import GetLog
from ddt import ddt,data
import json
import unittest
read_config = ReadConfig()
url_pre = read_config.get_config_str('api','hostName')
# 读取excel,获取login测试数据
read_excel = ReadExcel("data/123.xlsx")
recharge_cases = read_excel.get_cases("Sheet1")
get_log = GetLog()
@ddt
class TestRecharge(unittest.TestCase):
def setUp(self):
print('Test Start')
def tearDown(self):
print('Test End')
@data(*recharge_cases)
def test_recharge(self,case):
url = url_pre + case.url
token=Get_token().get_token()
try:
payload= json.loads(case.data)
payload["token"]=token
except:
payload=case.data
# 记录当前测试case信息
get_log.log_info('''Test Case Info:
case_id : {0}
title : {1}
method : {2}
url : {3}
data : {4}
expected: {5}
header : {6}
'''.format(case.case_id,case.title,case.method,url,payload,case.expected,case.header))
response = HttpRequest(method=case.method,url=url,data=payload,headers=eval(case.header))
actual = response.get_text()
# 记录当前测试case接口响应信息
get_log.log_info('''Test Case Request Response Result:
response : {0}
actual : {1}
'''.format(response.get_json(),actual))
#断言并将结果写入excel最后一行
try:
self.assertEquals(case.expected,actual)
read_excel.write_result('recharge',case.case_id,actual,'Pass')
get_log.log_info('Test Result is Passed ! case_id is {0},title is {1} '.format(case.case_id,case.title))
except Exception as e:
read_excel.write_result('recharge',case.case_id,actual,'Fail')
get_log.log_info('Test Result is Failed ! case_id is {0},title is {1} '.format(case.case_id,case.title))
get_log.log_error('Error msg :{0}'.format(e))
raise e
这套框架需要install的库有:
configparser==5.0.0
openpyxl==3.0.3
requests==2.23.0
pymysql==0.9.3
sshtunnel==0.1.5
ddt==1.4.1
HTMLTestRunner==0.8.0
一蓑一笠一扁舟
一丈丝纶一寸钩
一曲高歌一樽酒
一人独钓一江秋