python 接口自动化框架搭建二(unittest+mysql+python代码包+调试过程)

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

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

猜你喜欢

转载自blog.csdn.net/chang_jinling/article/details/88758702
今日推荐