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目录或包过程:

  1. 新建config目录,目录下新建一个数据库配置信息的文件db_config.ini,项目中一般数据库,redies等服务都是放到配置文件中的,如果下次数据库内容变了可以直接在这个配置文件中更新,内容如下:
[mysqlconf]
host = 127.0.0.1
port = 3306
user = root
password = 123456
db_name = interface_autotester
  1. 新建工具类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)
  1. 在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())
  1. 在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转换一下。
  2. 这样经过上面的步骤将数据库里面的数据读出来了,然后开始创建测试脚本,新建script目录,将自动生成的脚本放到这个目录下
  3. 新建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]))
'''
  1. 在interface包下新建public_info.py,放公用信息,文件里的所有内容对创建脚本里的所有逻辑都是公用的。
  2. 第九步前提:先在utils包下的static_final.py中写入script的绝对路径供公用(
# script目录的路径,即测试脚本文件存放目录
SCRIPT_PATH = BASE_DIR + "/script")
  1. 再在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文件,如截图:
  1. 继续补全上述代码:
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()
  1. 按照第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'}
  1. 继续完善第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'}
  1. 继续补全第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()
  1. 以上在create_script.py代码里添加的内容只是处理在static_final.py中对数据依赖的部分,即下截图中%s的数据依赖部分,并且把payload这个变量他的请求参数也直接给赋值回来了,变量名也定义了。接下来处理另外一个%s,他包含检查点和数据存储的部分,同样也需要在pubilc_info.py文件中去实现。
  2. 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]
  1. 依赖数据已经被存到了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()))
  1. 这两个方法建好后,再在这个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字段下了
  1. 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
  1. 接下来就可以在存依赖数据的时候把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. 框架1和框架2,里的的实现方式是可以随意组合的,即可以以连接数据库的形式但是不用unittest的方法而用框架1的方法,或者框架1不连excel连数据库也是可以的。
  2. 目前的接口测试都是单线程的,一般接口测试框架最好用队列来实现,python会支持celery,分布式队列。分布式队列相当于性能测试的并发。(celery服务在windows本地是搭不起来的)。
  3. 到目前为止,数据依赖和数据存储都已经实现了,接下来就可以做URL拼接写unittest脚本了。在interface的create_script.py文件中,定义一个变量去存拼接的code,叫做store_code,存储依赖数据是否需要写,是有条件的,我们需要看表interface_test_case表中的data_store中是否有内容,如果有的话就表示要做存储依赖数据的存储,如果没有的话就说明不需要存储,不用存储和需要存储时拼接的字符串是不一样的。所以需要做一下判断。
  4. 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()
  1. 执行create_script.py,如果脚本没有自动生成或者只生成了一部分,需要判断下写入条件是否满足,比如红框这里如果从数据库中取到的apiInfo[3]不是str类型,它和“post”比较的时候肯定是false,所以条件为false肯定是不会写入文件的,需要.decode()之后才能和字符串类型做对比:(根本原因是库里存的就是二进制的)
  2. 开始执行自动生成的脚本。
  3. 在根目录创建report目录,存放生成的测试报告的目录。
  4. 再utils中导入一个用于生成测试报告的.py文件。HTMLTestRunner.py。
  5. 再在工程的根目录创建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结果
  1. 运行后,script脚本中的.py文件被自动执行,并生成了HTML测试报告
  2. 查看运行结果:如果报错,接口测试中最容易出现内容类型不一致问题,需要深入研究,如果为了方便就将脚本中的中文替换为英文即可