1. 框架介绍:
新建python工程unittest_db_interface,用unittest连接db的接口测试,想要达到的最终效果是在script目录下全是unittest测试脚本,但是这些测试脚本不是手动写的,而是根据你在数据库里添加的测试用例自动生成的,因为有时会有用例管理的要求,就可以写成这样单个单元测试的脚本,然后去自动跑测试脚本,然后再出测试报告。
2. 前提条件:
安装mysql,方便数据库操作,如果未安装MYSQL服务,请参考下载:https://www.cnblogs.com/mituxiaogaoyang/p/12447966.html。连接数据库(通过数据库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":"[email protected]"}', None, 200, None, None, None, 0, None), (4, 1, '{"username":"c91","password":"c91","email":"[email protected]"}', 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'}
- 继续完善第11步:新添加的加了下划线:
- 继续补全第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测试报告
- 查看运行结果:如果报错,接口测试中最容易出现内容类型不一致问题,需要深入研究,如果为了方便就将脚本中的中文替换为英文即可
- 学习笔记,来自光荣之路测试开发培训。