1. 框架介绍:
新建python工程unittest_db_interface,用unittest连接db的接口测试,想要达到的最终效果是在script目录下全是unittest测试脚本,但是这些测试脚本不是手动写的,而是根据你在数据库里添加的测试用例自动生成的,因为有时会有用例管理的要求,就可以写成这样单个单元测试的脚本,然后去自动跑测试脚本,然后再出测试报告。
2. 前提条件:
安装mysql,方便数据库操作。连接数据库(通过数据库ip地址、用户名、密码、端口),在interface_autotester库中设计三个表,interface_api、interface_data_store、interface_test_case。其实这三个表中的字段和框架一中的字段是相互对应的
- 把要测试的接口都写到interface_api中
- 把测试用例写到interface_test_case中
- 把测试数据存储到interface_data_store中
3. 建库建表步骤以及为表中添加测试数据:
- 库名称interface_autotester,建库SQL语句: CREATE DATABASE IF NOT EXISTS interface_autotester;
- 库建好后在这个库下建立这三个表
- interface_api建表SQL语句: CREATE TABLE interface_api(
api_id INT NOT NULL AUTO_INCREMENT COMMENT "自增长主键",
api_name VARCHAR(50) NOT NULL COMMENT "接口的名字",
file_name VARCHAR(50) NOT NULL COMMENT "接口对应的测试用例",
r_url VARCHAR(50) NOT NULL COMMENT "请求接口的URL",
r_method VARBINARY(10) NOT NULL COMMENT "接口请求方式",
p_type VARCHAR(20) NOT NULL COMMENT "传参方式",
rely_db TINYINT DEFAULT 0 COMMENT "是否依赖数据库",
STATUS TINYINT DEFAULT 0,
ctime DATETIME,
UNIQUE INDEX(api_name),
PRIMARY KEY(api_id)
)ENGINE=INNODB DEFAULT CHARSET=utf8; - interface_test_case建表SQL语句: CREATE TABLE interface_test_case(
id INT NOT NULL AUTO_INCREMENT COMMENT "自增长主键",
api_id INT NOT NULL COMMENT "对应interface_api的api_id",
r_data VARCHAR(255) COMMENT "请求接口时传的参数",
rely_data VARCHAR(255) COMMENT "用例依赖的数据",
res_code INT COMMENT "接口期望响应code",
res_data VARCHAR(255) COMMENT "接口响应body",
data_store VARCHAR(255) COMMENT "依赖数据存储",
check_point VARCHAR(255) COMMENT "接口响应校验依据数据",
STATUS TINYINT DEFAULT 0 COMMENT "用例执行状态,0不执行,1执行",
ctime DATETIME,
PRIMARY KEY(id),
INDEX(api_id)
)ENGINE=INNODB DEFAULT CHARSET=utf8; - interface_data_store建表SQL语句: CREATE TABLE interface_data_store(
api_id INT NOT NULL COMMENT "对应interface_api的api_id",
case_id INT NOT NULL COMMENT "对应interface_test_case里的id",
data_store VARCHAR(255) COMMENT "存储的依赖数据",
ctime DATETIME,
INDEX(api_id,case_id)
)ENGINE=INNODB DEFAULT CHARSET=utf8; - 添加测试数据:通过add_test_data.py文件
4. 新建python目录或包过程:
- 新建config目录,目录下新建一个数据库配置信息的文件db_config.ini,项目中一般数据库,redies等服务都是放到配置文件中的,如果下次数据库内容变了可以直接在这个配置文件中更新,内容如下:
[mysqlconf]
host = 127.0.0.1
port = 3306
user = root
password = 123456
db_name = interface_autotester
- 新建工具类utils的python package,在utils下新建static_final.py文件,放公共的静态变量,这里添加了配置文件的变量:
# -*- coding:utf-8 -*-
import os
# 首先获取工程的根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 数据库配置文件绝对路径
config_path = BASE_DIR + "/config/db_config.ini"
# print(config_path)
- 在utils包下新建读取配置文件的config_handler.py文件,类ConfigParse是用来解析配置文件的,
# -*- coding:utf-8 -*-
import configparser # 解析配置文件的包,要导入该模块
from utils.static_final import config_path
# ConfigParse解析配置文件
class ConfigParse(object):
def __init__(self):
self.cf = ConfigParser.ConfigParser() # ConfigParser包的类ConfigParser,cf作为实例对象
def get_db_conf(self):
self.cf.read(config_path) # cf通过read方法把配置文件db_config.ini根据路径读出来
host = self.cf.get("mysqlconf", "host") # get在配置文件db_config.ini下的节点mysqlconf
port = self.cf.get("mysqlconf", "port")
db = self.cf.get("mysqlconf", "db_name")
user = self.cf.get("mysqlconf", "user")
password = self.cf.get("mysqlconf", "password")
return {"host": host, "port": int(port), "db": db, "user": user, "password": password}
if __name__ == "__main__":
cp = ConfigParse()
print(cp.get_db_conf())
- 在utils包下新建db_handler.py文件,来操作数据库, # -*- coding:utf-8 -*-
import pymysql
from utils.config_handler import ConfigParse
class DB(object):
def __init__(self):
self.db_conf = ConfigParse().get_db_conf()
self.conn = pymysql.connect(
host=self.db_conf["host"],
port=int(self.db_conf["port"]), #如果config_handler.py中已经将port int后才return,这里就不需要加了
user=self.db_conf["user"],
passwd=self.db_conf["password"],
db=self.db_conf["db"],
charset="utf8"
)
self.cur = self.conn.cursor() # 获取到游标,可以操作数据库
def close_connect(self):
# 关闭数据连接
self.conn.commit()
self.cur.close()
self.conn.close()
def get_api_list(self):
sqlStr = "select * from interface_api where status=1" # 是1表示要执行,0不执行
self.cur.execute(sqlStr)
# 返回tuple对象
apiList = list(self.cur.fetchall()) # 取出来是元组类型,将其转为list
return apiList
def get_api_case(self, api_id):
sqlStr = "select * from interface_test_case where api_id=%s" % api_id # 相当于取一个接口里的所有测试用例
self.cur.execute(sqlStr)
api_case_list = list(self.cur.fetchall())
return api_case_list
def get_rely_data(self, api_id, case_id):
sqlStr = "select data_store from interface_data_store where api_id=%s and case_id=%s" % (api_id, case_id)
self.cur.execute(sqlStr)
# 因为取出来的是字符串,所以转成字典对象
rely_data = eval((self.cur.fetchall())[0][0])
return rely_data
if __name__ == "__main__":
db = DB()
print("get_api_list:", db.get_api_list())
print("get_api_case:", db.get_api_case(1))
print("get_rely_data:", db.get_rely_data(1, 1))
调试结果:get_api_list: [(1, '用户注册', 'user_registration', 'http://xx.xxxx.xx.xx:xxxx/register/', b'post', 'data', 1, 1, None), (2, '用户登录', 'users_login', 'http://xx.xxxx.xx.xx:xxxx/login/', b'post', 'data', 1, 1, None), (3, '查询博文', 'get_blog', 'http://xx.xxxx.xx.xx:xxxx/getBlogContent/', b'get', 'url', 0, 1, None)] get_api_case: [(1, 1, '{"username":"zhangdongliang956","password":"zhangdongliang956","email":"zhangdongliang956@qq.com"}', None, 200, None, None, None, 0, None), (4, 1, '{"username":"c91","password":"c91","email":"c91@qq.com"}', None, 200, None, None, None, 0, None)] get_rely_data: {'username': 'zhangdongliang956', 'password': '30c98647110e5ed95213a33ed1d80c3b'}
如果上述划下划线粗体代码写成port=self.db_conf["port"] 且在config_handler.py中没有将port int后才return
则会在调试打印结果的时候报错:self.host_info = "socket %s:%d" % (self.host, self.port) TypeError: %d format: a number is required。这是由于端口号是按照字符串传来的,所以这里需要用int转换一下。
- 这样经过上面的步骤将数据库里面的数据读出来了,然后开始创建测试脚本,新建script目录,将自动生成的脚本放到这个目录下
- 新建python package名为interface,来实现生成测试脚本的逻辑,新建create_script.py文件。另外这里需要知道单元测试框架unittest知识点,因为自动生成的测试脚本都是.py文件,这些.py文件都是在create_script.py中生成后且在这些文件中写入unittest内容的代码。这些文件格式一致,有很多可以通用的代码,所以可以写到utils包下的static_final.py中。将通用代码写在static_final.py中:
# -*- coding:utf-8 -*-
import os
# 首先获取工程的根目录
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# 数据库配置文件绝对路径
config_path = BASE_DIR + "/config/db_config.ini"
# print(config_path)
###########从这里开始是新添加的代码##############
# unittest中通用的部分代码
code_head = '''#encoding = utf-8
import unittest, requests
from interface.public_info import *
import os, sys, json
'''
# 无数据库连接时,初始化方法就没有必要写self.dbd = DB_Data()
class_head = '''
class %s (unittest.TestCase):
"""%s""" #备注信息,类似于"用户注册接口"这样的备注信息
def setUp(self):
self.base_url = "%s"
'''
# 有数据库连接时
class_head_db = '''
class %s (unittest.TestCase):
"""%s"""
def setUp(self):
self.dbd = DB_Data()
self.base_url = "%s"
'''
# 关闭数据库,如果有数据库连接时,就也要用到关闭数据库连接
class_end_db = '''
def tearDown(self):
self.dbd.close_connect()
'''
# 如果要执行.py文件,就需要加入这行代码
code_end = '''
if __name__ == '__main__':
unittest.main()
'''
post_code = '''
def test_%s(self):
"""%s"""
%s # 这里相当于测试数据或者数据存储的内容
r = requests.post(self.base_url, data = json.dumps(%s))
result = r.json()
self.assertEqual(result.status_code, 200)
%s # 这里相当于监测点
'''
get_code = '''\n
def test_%s(self):
"""%s"""
%s
r = requests.get(self.base_url + str(payload))
result = r.json()
self.assertEqual(r.status_code, 200)
%s # 这里相当于监测点
'''
check_code = '''
check_point = %s
for key, value in check_point.items():
self.assertEqual(result[key], value, msg = "字段【{}】: exception: {}, reality : {}".format(key, value, result[key]))
'''
- 在interface包下新建public_info.py,放公用信息,文件里的所有内容对创建脚本里的所有逻辑都是公用的。
- 第九步前提:先在utils包下的static_final.py中写入script的绝对路径供公用(
# script目录的路径,即测试脚本文件存放目录
SCRIPT_PATH = BASE_DIR + "/script")
- 再在interface包下写create_script.py文件:
# -*- coding:utf-8 -*-
from utils.db_handler import DB
from utils.static_final import *
import sys
from importlib import reload
reload(sys)
# 通过接口名、请求地址、测试用例等生成unittest的测试文件,新建函数new_file
# 测试文件要放到script目录下,所以需要知道script路径,在静态文件中设置其路径
def new_file(apiInfo, api_case_list): # 把apiInfo接口里的所有api_case_list测试用例取出来
# 需要区分是哪个接口的测试文件,可以通过api_name和file_name指定
# print(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py")
# 结果:
# C:\Users\太阳\PycharmProjects\untitled4 / script / user_registration_test.py
# C:\Users\太阳\PycharmProjects\untitled4 / script / users_login_test.py
# C:\Users\太阳\PycharmProjects\untitled4 / script / get_blog_test.py
with open(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py", "w") as fp: # 通过unittest执行的时候需要有个test标识,如果是test结尾的就执行
fp.write(code_head) # 写头部
if apiInfo[5] == 1:
# 表示需要连接数据库
fp.write(class_head_db % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
else:
# 表示不需要连接数据库
fp.write(class_head % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
fp.close()
def create_script(): # 读数据库的主方法
db = DB()
# 从数据库获取需要执行的api列表,在表interface_api表中
apiList = db.get_api_list() # 获取到所有的接口
for api in apiList:
# 获取到接口后还需要获取到所有接口的测试用例,所以接下来是根据api_id获取该接口的测试用例
api_case_list = db.get_api_case(api[0]) # api[0]对应的就是interface_api表的api_id列
# print(api_case_list)
new_file(api[1:7], api_case_list) # 表中第六列以后无多大用处,所有就不取了
if __name__ == "__main__":
create_script() 写到这里后,可以调试以下,这里执行这个文件后,应该在script目录下新建好了三个.py文件,如截图:
- 继续补全上述代码:
def new_file(apiInfo, api_case_list): # 把apiInfo接口里的所有api_case_list测试用例取出来
# 需要区分是哪个接口的测试文件,可以通过api_name和file_name指定
# print(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py")
# 结果:
# C:\Users\太阳\PycharmProjects\untitled4 / script / user_registration_test.py
# C:\Users\太阳\PycharmProjects\untitled4 / script / users_login_test.py
# C:\Users\太阳\PycharmProjects\untitled4 / script / get_blog_test.py
with open(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py", "w") as fp: # 通过unittest执行的时候需要有个test标识,如果是test结尾的就执行
fp.write(code_head) # 写头部
if apiInfo[5] == 1:
# 表示需要连接数据库
fp.write(class_head_db % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
else:
# 表示不需要连接数据库
fp.write(class_head % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
for idx, case in enumerate(api_case_list, 1):
#判断是否需要做依赖数据处理
if case[3]:
# 需要获取依赖数据
# 这里先去完善一下interface包下的public_info.py文件
else:
# 不需要进行依赖数据处理
fp.close()
- 按照第10步中绿色字体提示,我们先去完善public_info.py脚本,即上面第7步新建的文件,完善如下:
# -*- coding:utf-8 -*-
import pymysql
from utils.config_handler import ConfigParse
class DB_Data(object):
def __init__(self):
self.db_conf = ConfigParse().get_db_conf()
self.conn = pymysql.connect(
host=self.db_conf["host"],
port=int(self.db_conf["port"]),
user=self.db_conf["user"],
passwd=self.db_conf["password"],
db=self.db_conf["db"],
charset="utf8"
)
# 设置数据库变更自动提交
self.conn.autocommit(1)
self.cur = self.conn.cursor() # 获取到游标
def close_connect(self):
# 关闭数据连接
self.conn.commit()
self.cur.close()
self.conn.close()
def get_rely_data(self, api_id, case_id):
sqlStr = "select data_store from interface_data_store where api_id=%s and case_id=%s" % (api_id, case_id)
self.cur.execute(sqlStr)
# 因为取出来的是字符串,所以转成字典对象
rely_data = eval((self.cur.fetchall())[0][0])
return rely_data
def param_completed(self, param, rely_data):
"""处理依赖数据,使请求参数完整化"""
paramSource = param.copy()
for key, value in rely_data.items():
api_id, case_id = key.split("->")
relyData = self.get_rely_data(int(api_id), int(case_id))
print(relyData)
if __name__ == "__main__":
dbd = DB_Data()
param = {"username": "changjinling2", "password": "changjinling123452"}
rely_data = {"1->1": ["username"]}
dbd.param_completed(param, rely_data) 调试结果:{'username': 'zhangdongliang956', 'password': '30c98647110e5ed95213a33ed1d80c3b'}
- 继续完善第11步:新添加的加了下划线:
# -*- coding:utf-8 -*-
import pymysql
from utils.config_handler import ConfigParse
class DB_Data(object):
def __init__(self):
self.db_conf = ConfigParse().get_db_conf()
self.conn = pymysql.connect(
host=self.db_conf["host"],
port=int(self.db_conf["port"]),
user=self.db_conf["user"],
passwd=self.db_conf["password"],
db=self.db_conf["db"],
charset="utf8"
)
# 设置数据库变更自动提交
self.conn.autocommit(1)
self.cur = self.conn.cursor() # 获取到游标
def close_connect(self):
# 关闭数据连接
self.conn.commit()
self.cur.close()
self.conn.close()
def get_rely_data(self, api_id, case_id):
sqlStr = "select data_store from interface_data_store where api_id=%s and case_id=%s" % (api_id, case_id)
self.cur.execute(sqlStr)
# 因为取出来的是字符串,所以转成字典对象
rely_data = eval((self.cur.fetchall())[0][0])
return rely_data
def param_completed(self, param, rely_data):
"""处理依赖数据,使请求参数完整化"""
paramSource = param.copy()
for key, value in rely_data.items():
api_id, case_id = key.split("->")
relyData = self.get_rely_data(int(api_id), int(case_id))
# print(relyData)
for k, v in relyData.items():
# 把依赖数据拿到后需要将其set回原来的请求变量中
if k in paramSource:
paramSource[k] = v
# 返回的这个参数就是发送请求时真正想用到的参数。
return paramSource
if __name__ == "__main__":
dbd = DB_Data()
param = {"username": "changjinling2", "password": "changjinling123452"}
rely_data = {"1->1": ["username"]}
dbd.param_completed(param, rely_data)
print(dbd.param_completed(param, rely_data))打印调试一下是否通: {'username': 'zhangdongliang956', 'password': '30c98647110e5ed95213a33ed1d80c3b'}
- 继续补全第10步代码:
with open(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py", "w") as fp: # 通过unittest执行的时候需要有个test标识,如果是test结尾的就执行
fp.write(code_head) # 写头部
if apiInfo[5] == 1:
# 表示需要连接数据库
fp.write(class_head_db % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
else:
# 表示不需要连接数据库
fp.write(class_head % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
param_code = ""
for idx, case in enumerate(api_case_list, 1):
# 判断是否需要做依赖数据处理
if case[3]:
# 需要获取依赖数据
# 这里先去完善一下interface包下的public_info.py文件,完善完后回来这里,先声明一个变量param_code,再继续
param_code = """payload = self.dbd.param_completed(%s, %s)""" %(eval(case[2]),eval(case[3]))
else:
# 不需要进行依赖数据处理
param_code = """payload = %s""" % case[2]
fp.close()
- 以上在create_script.py代码里添加的内容只是处理在static_final.py中对数据依赖的部分,即下截图中%s的数据依赖部分,并且把payload这个变量他的请求参数也直接给赋值回来了,变量名也定义了。接下来处理另外一个%s,他包含检查点和数据存储的部分,同样也需要在pubilc_info.py文件中去实现。
- public_info.py文件代码更新如下:
# 定义一个数据存储的方法
def store_data(self, api_id, case_id, storeReg, requestData = None, responseData = None):
"""存储依赖数据"""
# storeReg是一个字典对象,代表数据存储规则
storeData = {} # 空的字典对象存放要最终存到数据库中的依赖数据的一些字段值
# 以下for循环是遍历存储规则storeReg这个字典对象,即对应interface_test_case表中data_store列
for key, value in storeReg.items():
if requestData and key == "request": # 如果是request则要去请求参数requestData里取数据
for i in value:
if i in requestData:
storeData[i] = requestData[i]
elif responseData and key == "response":
for j in value:
if j in responseData:
storeData[j] = responseData[j]
- 依赖数据已经被存到了storeData字典中了,接下来就是把依赖数据入库,先新建一个方法,叫in_store_data, in_store_data又要用到has_rely_data方法来判断是否有依赖数据,所有新建两个方法:
def has_rely_data(self, api_id, case_id):
sqlStr = "select data_store from interface_data_store where api_id=%s and case_id=%s" %(api_id,case_id)
# 获取受影响的行数,0表示没有查到数据,大于0表示至少有一条数据
affect_num = self.cur.execute(sqlStr)
# 返回受影响的条数
return affect_num
def in_store_data(self, api_id, case_id, storeData):
"""向数据库中写入依赖数据"""
# 判断数据库中是否已存在这条数据,新建方法has_rely_Data,如果存在则更新,不存在则添加
has_data = self.has_rely_data(api_id, case_id)
if has_data:
# 表示数据库中已经存在这条数据,直接更新data_store字段即可
sqlStr = "update interface_data_store set data_store=\"%s\", ctime=\"%s\" where api_id=%s and case_id=%s"
self.cur.execute(sqlStr % (storeData, datetime.now(), api_id, case_id))
else:
# 数据库中不存在这条数据,需要添加
sqlStr = "insert into interface_data_store value(%s, %s, \"%s\", \"%s\")"
self.cur.execute(sqlStr % (api_id, case_id, storeData, datetime.now()))
- 这两个方法建好后,再在这个public_info.py文件的已经建好的store_data中,调用:############标记
# 定义一个数据存储的方法
def store_data(self, api_id, case_id, storeReg, requestData = None, responseData = None):
"""存储依赖数据"""
# storeReg是一个字典对象,代表数据存储规则
storeData = {} # 空的字典对象存放要最终存到数据库中的依赖数据的一些字段值
# 以下for循环是遍历存储规则storeReg这个字典对象,即对应interface_test_case表中data_store列
for key, value in storeReg.items():
if requestData and key == "request": # 如果是request则要去请求参数requestData里取数据
for i in value:
if i in requestData:
storeData[i] = requestData[i]
elif responseData and key == "response":
for j in value:
if j in responseData:
storeData[j] = responseData[j]
############# 将依赖数据写入到数据库,先新建一个方法,在上面位置叫in_store_data
self.in_store_data(api_id, case_id, storeData) 调试:
if __name__ == "__main__":
dbd = DB_Data()
param = {"username": "changjinling2", "password": "changjinling123452"}
rely_data = {"1->1": ["username"]}
# print(dbd.param_completed(param, rely_data))
dbd.store_data(2, 1, {"request": ["userid", "token"]}, {"userid": 12, "token": "changjinling"})
结果可以从数据库中可以看出来:结果{"userid": 12, "token": "changjinling"}已经被存入了数据库interface_data_store表中的data_store字段下了
- MD5加密,在Utils包中新建文件名字叫Md5_encrypt.py。
# -*- coding:utf-8 -*-
import hashlib
def md5_encrypt(text):
"""md5加密"""
md5 = hashlib.md5()
text = text.encode('utf-8')
md5.update(text)
return md5.hexdigest()
if __name__ == "__main__":
print(md5_encrypt("test")) 调试结果:098f6bcd4621d373cade4e832627b4f6
- 接下来就可以在存依赖数据的时候把MD5加上了,在存密码字段的时候加密以下,保证存到数据库中的就是加密过了的,在public_data.py文件中更新方法store_data:
# 定义一个数据存储的方法
def store_data(self, api_id, case_id, storeReg, requestData = None, responseData = None):
"""存储依赖数据"""
# storeReg是一个字典对象,代表数据存储规则
storeData = {} # 空的字典对象存放要最终存到数据库中的依赖数据的一些字段值
# 以下for循环是遍历存储规则storeReg这个字典对象,即对应interface_test_case表中data_store列
for key, value in storeReg.items():
if requestData and key == "request": # 如果是request则要去请求参数requestData里取数据
for i in value:
if i in requestData:
if i == "password":
storeData[i] = md5_encrypt(requestData[i])
else:
storeData[i] = requestData[i]
elif responseData and key == "response":
for j in value:
if j in responseData:
if j == "password":
storeData[j] = md5_encrypt(requestData[j])
else:
storeData[j] = requestData[j] 调试一下:
if __name__ == "__main__":
dbd = DB_Data()
param = {"username": "changjinling2", "password": "changjinling123452"}
rely_data = {"1->1": ["username"]}
# print(dbd.param_completed(param, rely_data))
dbd.store_data(2, 1, {"request": ["userid", "token", "password"]}, {"userid": 12, "token": "changjinling", "password":"lihao716@"}) 运行结果没有报错,然后在数据库中可以看到,密码被md5加密后保存到了数据库中:参考如图:
- 框架1和框架2,里的的实现方式是可以随意组合的,即可以以连接数据库的形式但是不用unittest的方法而用框架1的方法,或者框架1不连excel连数据库也是可以的。
- 目前的接口测试都是单线程的,一般接口测试框架最好用队列来实现,python会支持celery,分布式队列。分布式队列相当于性能测试的并发。(celery服务在windows本地是搭不起来的)。
- 到目前为止,数据依赖和数据存储都已经实现了,接下来就可以做URL拼接写unittest脚本了。在interface的create_script.py文件中,定义一个变量去存拼接的code,叫做store_code,存储依赖数据是否需要写,是有条件的,我们需要看表interface_test_case表中的data_store中是否有内容,如果有的话就表示要做存储依赖数据的存储,如果没有的话就说明不需要存储,不用存储和需要存储时拼接的字符串是不一样的。所以需要做一下判断。
- create_script.py文件全代码:
# -*- coding:utf-8 -*-
from utils.db_handler import DB
from utils.static_final import *
def new_file(apiInfo, api_case_list):
with open(SCRIPT_PATH + "/" + apiInfo[1] + "_test.py", "w") as fp:
fp.write(code_head)
if apiInfo[5] == 1:
fp.write(class_head_db % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
else:
fp.write(class_head % (apiInfo[1].title(), apiInfo[0], apiInfo[2]))
param_code = ""
for idx, case in enumerate(api_case_list, 1):
# print("%s:%s" % (idx, case))
if case[3]:
param_code = '''payload = self.dbd.param_completed(%s, %s)''' % (eval(case[2]), eval(case[3]))
else:
param_code = '''payload = %s''' % case[2]
store_code = ""
if case[6]:
store_code = '''self.dbd.store_data(%s, %s, %s, %s, %s)''' % (int(case[1]), int(case[0]), case[6], case[2] if case[2] else None, "result")
if case[7]:
store_code += check_code % case[7]
# print(type(apiInfo[3]))
# print(apiInfo[3] == "post")
if apiInfo[3].decode() == "post":
fp.write(post_code % (apiInfo[1] + "_" + str(idx), str(idx), param_code, store_code))
elif apiInfo[3].decode() == "get":
fp.write(get_code % (apiInfo[1] + "_" + str(idx), str(idx), param_code, store_code))
if apiInfo[5] == 1:
fp.write(class_end_db)
fp.write(code_end)
fp.close()
def create_script():
db = DB()
apiList = db.get_api_list()
for api in apiList:
api_case_list = db.get_api_case(api[0])
new_file(api[1:7], api_case_list)
if __name__ == "__main__":
create_script()
- 执行create_script.py,如果脚本没有自动生成或者只生成了一部分,需要判断下写入条件是否满足,比如红框这里如果从数据库中取到的apiInfo[3]不是str类型,它和“post”比较的时候肯定是false,所以条件为false肯定是不会写入文件的,需要.decode()之后才能和字符串类型做对比:(根本原因是库里存的就是二进制的)
- 开始执行自动生成的脚本。
- 在根目录创建report目录,存放生成的测试报告的目录。
- 再utils中导入一个用于生成测试报告的.py文件。HTMLTestRunner.py。
- 再在工程的根目录创建run_test.py文件。
# -*- coding:utf-8 -*-
import sys
sys.path.append("./script") # 把当前根目录下的script目录加到环境变量中
from utils.HTMLTestRunner import HTMLTestRunner # 生成测试报告
from unittest import defaultTestLoader # 加载测试用例的
from interface.create_script import create_script # 生成脚本的方法
import time
# reload(sys)
# sys.setdefaultencoding("utf8")
if __name__ == "__main__":
# 生成测试脚本
create_script()
# 执行测试脚本
now = time.strftime("%Y-%m-%d %H_%M_%S")
# 指定测试用例为当前文件夹下的script目录
test_dir = './script'
testsuit = defaultTestLoader.discover(test_dir, pattern='*_test.py') # 去script目录下找_test.py结尾的文件,把他加载成测试用例组赋给变量testsuit
filename = './report/' + now + '_result.html'
fp = open(filename, "wb")
runner = HTMLTestRunner(stream=fp, title="接口自动化测试", description="接口自动化测试结果报告")
runner.run(testsuit)
fp.close() 在report目录下查看生成的HTML结果
- 运行后,script脚本中的.py文件被自动执行,并生成了HTML测试报告
- 查看运行结果:如果报错,接口测试中最容易出现内容类型不一致问题,需要深入研究,如果为了方便就将脚本中的中文替换为英文即可