新建项目及python包
创建一个新的python项目,在下面建一个run.py文件作为unittest的TestRunner来跑测试用例;再新建一个tests包放测试用例:
新建config包来存放配置文件:
新建libs包存放第三方模块:
新建reports包存放执行测试用例后生成的测试报告:
新建data包存放调用接口和断言时所需要准备的测试数据:
新建common包存放公共模块:
新建logs包存放日志:
跑通用例
接着在tests包下新建一个test_demo.py,先不写任何实质上的内容,仅仅是个为了测试的空壳。
- test_demo.py
import unittest
class Test_demo(unittest.TestCase):
#先什么都不写,pass当做这个用例100%能执行通过!
def test_demo(self):
pass
接着我们就开始写run.py
- run.py
import unittest
import os
from datetime import datetime
from config import config
from libs.HTMLTestRunnerNew import HTMLTestRunner
#加载用例
loader = unittest.TestLoader()
#自动检索所有测试用例
"""
问题1:路径的重用
这里我们准备传入测试用例所在路径:tests,直接在这里用变量定义出来自然是最简单的,
但后面可能还需要更多需要传入路径的场景,所以我们需要找个地方配置所有的路径。而一
想到配置,我们就会想到放到config目录下。但具体我们应该用什么配置呢?yaml吗?但
yaml只适合配置静态的配置,路径是会随着项目的转移而发生变化,所以我们可以用一个py
文件来配置所有的路径信息。在config.py定义了测试用例的路径后,我们可以直接import
过来。
@路径配置详见config.py
"""
cases = loader.discover(config.CASES_PATH)
"""
问题2:报告时间戳格式的生成
这里我们需要定义报告的名称。而每次生成的报告如果以同一文件名(如:report.html)命名
的话,就会覆盖之前的报告内容。当我们需要找回历史报告(如上个月的报告)时就找不到了,所以
我们需要用时间戳格式命名报告的文件名,这样不仅执行时间可以一目了然,还能找到对应时间的
报告回顾。
"""
#定义时间戳
time_format = datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
#测试报告文件名称
report_name = "report-{}.html".format(time_format)
#测试报告路径
report_path = os.path.join(config.REPORT_PATH,report_name)
#执行测试用例
with open(report_path,mode='wb') as f:
#这里我们用第三方模块:HTMLTestRunnerNew来生成测试报告
runner = HTMLTestRunner(
f,
title = "接口自动化测试报告",
description = "测试报告",
tester = "tom"
)
#执行测试用例
runner.run(cases)
问题1中我们需要把项目中的所有包的路径都存放到py文件中,我们需要在包里面新建一个config.py:
- config.py
import os
#配置文件路径
CONFIG_PATH = os.path.dirname(os.path.abspath(__file__))
#项目路径
ROOT_PATH = os.path.dirname(CONFIG_PATH)
#测试用例路径
CASES_PATH = os.path.join(ROOT_PATH,"tests")
#测试报告路径
REPORT_PATH = os.path.join(ROOT_PATH,"reports")
在libs包下存放刚刚用到的第三方模块HTMLTestRunnerNew.py:
各个包、TestCase、TestRunner都准备好后,接下来我们去到run.py执行测tests下的所有测试用例,执行结果如下,表明unittest中的TestCase和TestRunner是能跑通的:
而如果运行时报了下面的错,则表明测试用例是有问题的,要么是缺少__init__.py文件,要么是测试用例命名不规范(注意,命名最好以test开头),要么是测试用例内部的代码出了问题:
通用模块的构建
我们需要把之前讲到的各个测试框架的组件加到common中,包括:excel_handler、logging_handler、yaml_handler、request_handler、sql_handler:
excel_handler
import openpyxl
import pprint
class excel_handler():
"""初始化实例参数"""
def __init__(self,filepath):
self.filepath = filepath
self.workbook = None
"""定义open_file方法,返回workbook对象"""
def open_file(self):
workbook = openpyxl.load_workbook(self.filepath)
self.workbook = workbook
return workbook
"""通过workbook对象获取sheet对象"""
def get_sheet(self,name):
workbook = self.open_file()
sheet = workbook[name]
return sheet
"""获取表单里的每一个单元格对象的value"""
def get_data(self,name):
#获取表单对象
sheet = self.get_sheet(name)
#获取表单对象中的所有行
rows = list(sheet.rows)
#定义一个列表,用来接收每一行的值
data = []
#定义一个列表,获取所有的标题
tittles = []
#遍历第一行rows[0],获取所有行的value属性,放入tittles中
for tittle in rows[0]:
tittles.append(tittle.value)
#遍历第二行后的所有数据(排除掉第一行是因为已经遍历完了第一行的标题)
for row in rows[1:]:
#定义一个字典,存放每一行的值,每一个值是一个键值对,key是标题,值是该行对应的value属性。
row_data = {}
#遍历拿到的每一行,获取每一行的每一个单元格放入row_data中.其中key为标题,value为该单元格对应的值.
for index,cell in enumerate(row):
row_data[tittles[index]] = cell.value
#把每一行的数据放入data中
data.append(row_data)
return data
"""对单元格的value属性赋值,写入到excel中"""
def write(self, sheet_name, row, column, data):
sheet = self.get_sheet(sheet_name)
sheet.cell(row, column).value = data
self.save()
self.close()
"""对excel做修改后,保存excel文件"""
def save(self):
self.workbook.save(self.file_path)
"""关闭workbook对象,释放内存"""
def close(self):
self.workbook.close()
logging_handler
import logging
def log_handler(
logger_name = "logger",
logger_level = "DEBUG",
stream_level = "DEBUG",
file_name = "None",
file_level = "DEBUG",
fmt = '%(asctime)s--%(filename)s--No:%(lineno)d--%(levelname)s:%(message)s',
):
#初始化日志收集器
logger = logging.getLogger(logger_name)
#设置日志收集器的日志级别
logger.setLevel(logger_level)
#初始化流处理器
stream_handler = logging.StreamHandler()
#设置流处理器的日志级别
stream_handler.setLevel(stream_level)
#将流处理器与日志收集器绑定起来
logger.addHandler(stream_handler)
#设置格式
fmt = logging.Formatter(fmt)
#设置流处理器的日志输出格式
stream_handler.setFormatter(fmt)
#如果传入的file_name不为空,则初始化文件处理器
if file_name:
#初始化文件处理器
file_handler = logging.FileHandler(file_name,encoding='UTF-8')
#设置文件处理器的日志级别
file_handler.setLevel(file_level)
#将文件处理器与日志收集器绑定起来
logger.addHandler(file_handler)
#设置文件处理器的日志输出格式
file_handler.setFormatter(fmt)
return logger
yaml_handler
import yaml
def read_yaml(file):
"""读取 yaml 文件"""
with open(file, encoding='utf8') as f:
conf = yaml.load(f, Loader=yaml.SafeLoader)
return conf
def write_yaml(file, data):
with open(file, 'w', encoding='utf8') as f:
yaml.dump(data, f)
request_handler
import requests
import logging
def requests_handler(
#准备请求接口时所需要的参数:请求地址、请求方法、请求头、url参数、form参数、json参数
url,
method = "get",
headers = None,
params = None,
data = None,
json = None,
):
"""由于requests里面封装的各种请求方法(get、post、put、delete、head等)最后都要调用request方法,所以
可以直接调用requests完成所有请求方法的调用,而不用再一个个请求方法封装"""
req = requests.request(method,
url,
data = data,
json = json,
params = params,
headers = headers
)
try:
#由于接口最后返回的都是json格式的数据,所以这里调用json方法返回json格式数据
return req.json()
except Exception as e:
#如果调用json方法报错,则说明返回格式不是json格式,这时应该抛异常
logging.error("返回的格式不是json格式{}".format(e))
return None
sql_handler
import pymysql
from pymysql.cursors import DictCursor
class mysql_handler():
#初始化,准备需要建立数据库连接的所有参数
def __init__(
self,
host = None,
port = 3306,
user = None,
password = None,
charset="utf8",
cursorclass=DictCursor
):
#建立数据库连接,传入准备好的参数
self.con = pymysql.connect(
host=host,
port=port,
user=user,
password=password,
charset=charset,
cursorclass=cursorclass
)
self.cursor = self.con.cursor()
#查询方法,传入需要执行查询的sql,以及控制是选择fetchall还是fetchone的参数is_one
def query(self,sql,is_one=True):
#调用游标对象的execute方法执行查询的sql
self.cursor.execute(sql)
#如果is_one为true,则只返回结果中的第一条记录
if is_one:
return self.cursor.fetchone()
#如果is_one为false,则返回结果中的所有记录
return self.cursor.fetchall()
#关闭游标对象及连接对象
def close(self):
self.cursor.close()
self.con.close()