zoukankan      html  css  js  c++  java
  • Python3 + requests + unittest接口测试

    一、缘 起

    笔者最近完成了基于Python3 + requests + unittest的接口测试脚本,故在此做一下记录,于己为复盘,于彼为学习和参考

    二、思 路

    接口测试无非三步:

    • 首先,造数据 - Python3连接MySQL,插入测试数据
    • 然后,发请求 - requests模块依次发请求
    • 最后,校验返回值 - 基于unittest框架,assert主要的response

    三、说 明

    脚本的工程名为zhtyInterfaceTest,整体结构如下图
    脚本结构

    1. .ideaPyChram编辑器自动生成的,不用管
    2. common为通用的模块/方法集合,如接口加密方法、配置parameters方法、配置数据库方法、读Excel方法,等,一切你能想到的辅助测试的方法都可以放到这里
    3. testCase为测试测试用例集合,笔者用unittest框架、用discover方法添加测试用例,那么想按顺序执行测试用例,就必须对测试用例命名按照ASCII顺序,具体如下图
      测试用例命名
    4. testFile存放测试数据和测试用图,测试数据为Excel格式
    5. testResult为测试结果集合,基于unittest框架,运行完脚本直接生成HTML格式的测试报告
    6. config.ini为配置文件,配置需要测试的URL、数据库及SQL语句
    7. runAll.py为主程序

    四、实 现

    1. 造测试数据,如果读者对Python3操作数据库不甚熟悉,可以参考笔者之前写的文章《Python3连接MySQL数据库及基本操作
    • ./zhtyInterfaceTest/common/mysqlCfg.py
    #!/usr/bin/python
    # coding=utf-8
    
    from pymysql import connect
    from pymysql.err import OperationalError
    import os
    import configparser as cparser
    
    # ------------------读取配置文件及数据库配置----------------------
    base_path = str(os.path.dirname(os.path.dirname(__file__)))
    base_path = base_path.replace('\', '/')
    cfg_path = base_path + '/config.ini'
    cf = cparser.ConfigParser()
    cf.read(cfg_path, encoding='utf-8')
    host = cf.get('MYSQL', 'host')
    port = cf.get('MYSQL', 'port')
    user = cf.get('MYSQL', 'user')
    password = cf.get('MYSQL', 'password')
    db_name = cf.get('MYSQL', 'db_name')
    charset = cf.get('MYSQL', 'charset')
    
    # -----------封装MySQL数据库基本操作-----------------
    
    
    class DB:
    
        def __init__(self):
    
            try:
                # 连接数据库
                self.conn = connect(
                    host=host,
                    port=int(port),
                    user=user,
                    password=password,
                    db=db_name,
                    charset=charset
                )
            except OperationalError as e:
                print('MySQL error %d: %s' % (e.args[0], e.args[1]))
            else:
                self.cursor = self.conn.cursor()
    
        def execute_sql(self, command, sql):
            """
            插入表数据
            :param command:
            :param sql:
            :return:
            """
            if command in ('SELECT', 'select'):
                # 如果为查询指令
                sql = sql.encode('utf-8')
                try:
                    self.cursor.execute(sql)
                    result = self.cursor.fetchall()
                    return result
                except Exception as e:
                    print(e)
                finally:
                    self.conn.close()
            elif command in ('delete', 'DELETE', 'update', 'UPDATE', 'insert', 'INSERT'):
                # 如果为增删改
                sql = sql.encode('utf-8')
                try:
                    self.cursor.execute(sql)
                    self.conn.commit()
                except Exception as e:
                    self.conn.rollback()
                    print(e)
                finally:
                    self.conn.close()
            else:
                print('Command Error!')
    
    
    if __name__ == '__main__':
    
        # sel_sql = cf.get('SQL', 'SELECT')
        # s = DB().execute_sql('select', sel_sql)
        print(DB().execute_sql('select', cf.get('SQL', 'SELECT'))[0][0])
    
    • ./zhtyInterfaceTest/runAll.py
    #!/usr/bin/python
    # coding=utf-8
    
    from HTMLTestRunner import HTMLTestRunner
    import configparser as cparser
    from common.mysqlCfg import DB
    import unittest
    import time
    import os
    
    discover = unittest.defaultTestLoader.discover('./testCase/teacher', pattern='test*.py')
    
    if __name__ == '__main__':
    
        # ------------------读取配置文件----------------------
        base_path = str(os.path.dirname(__file__))
        base_path = base_path.replace('\', '/')
        cfg_path = base_path + '/config.ini'
        cf = cparser.ConfigParser()
        cf.read(cfg_path, encoding='utf-8')
    
        # 初始化测试数据
        ins_tb_user = cf.get('SQL', 'ins_tb_user')
        ins_relation_class = cf.get('SQL', 'ins_relation_class')
        ins_sport_data_01 = cf.get('SQL', 'ins_sport_data_01')
        ins_sport_data_02 = cf.get('SQL', 'ins_sport_data_02')
        ins_class_course_data = cf.get('SQL', 'ins_class_course_data')
        # 执行SQL
        DB().execute_sql('insert', ins_tb_user)
        DB().execute_sql('insert', ins_relation_class)
        DB().execute_sql('insert', ins_sport_data_01)
        DB().execute_sql('insert', ins_sport_data_02)
        DB().execute_sql('insert', ins_class_course_data)
    
    ~
    ......
    ~
    
    【脚 本 说 明】
    • 通过读取config.ini文件的数据库配置信息和需要执行的SQL语句,实现连接数据库以及执行SQL的方法
    • 在主程序runAll.py执行测试用例前提前造好测试数据
    1. request请求
    • ./zhtyInterfaceTest/reqMethod.py
    #!/usr/bin/python
    # coding=utf-8
    
    import configparser as cparser
    import requests
    import os
    
    # ------------------读取配置文件----------------------
    base_path = str(os.path.dirname(os.path.dirname(__file__)))
    base_path = base_path.replace('\', '/')
    cfg_path = base_path + '/config.ini'
    cf = cparser.ConfigParser()
    cf.read(cfg_path, encoding='utf-8')
    
    
    class RequestMethod:
        """ 定义请求类型 """
    
        def __init__(self):
    
            """初始化参数"""
            self.base_url = cf.get('URL', 'base_url')
            self.data = {}
            self.files = {}
    
        def get(self, url, params):
            """
            定义get方法请求
            :return:
            """
            test_url = self.base_url + url
            try:
                return requests.get(url=test_url, params=params, timeout=60)
            except TimeoutError:
                return print('%s get request timeout!' % url)
    
        def post(self, url, params):
            """
            定义post方法请求
            :return:
            """
            test_url = self.base_url + url
            try:
                return requests.post(url=test_url, data=params, timeout=60)
            except TimeoutError:
                return print('%s post request timeout!' % url)
    
        def post_with_file(self, url, params, fp):
            """
            定义post方法请求
            :return:
            """
            test_url = self.base_url + url
            file = {
                'head_img': open(fp, 'rb')
            }
            try:
                return requests.post(url=test_url, data=params, files=file, timeout=60)
            except TimeoutError:
                return print('%s post request timeout!' % url)
    
    • ./zhtyInterfaceTest/testCase/teacher/test_1_login.py
    #!/usr/bin/python
    # coding=utf-8
    
    from common.reqMethod import RequestMethod
    from common.getParams import *
    import unittest
    
    
    class Login(unittest.TestCase):
    
        """登录接口测试"""
    
        def setUp(self):
            self.url = 'login'
            self.sheet = 'login'
    
        def tearDown(self):
            print(self.req_result)
    
        def test_login_success(self):
            """
            测试正常登录
            :return:
            """
            case = 'test_login_success'
            # 获取请求参数
            param = get_req_params(self.sheet, case)
            # 发起请求
            self.req_result = RequestMethod().post(self.url, param).json()
    ~
    ......
    ~
    
    【脚 本 说 明】
    • 读配置文件获取基础URL,然后依次定义三种常用的HTTP请求方法:getpostpost_with_file
    • 根据请求方法的不同,传参的类型一般为:测试接口(与基础URL拼接为请求URL)、paramsfp(上传文件的路径)
    • 在每一个测试用例中,通过调用getParams.py模块的get_req_params方法,从Excel获取请求参数,通过get_resp_params方法从Excel获取响应参数
    • 在测试用例中调用reqMethod.py中封装的请求方法,发接口请求
    1. 校验返回值
    • ./zhtyInterfaceTest/testCase/teacher/test_1_login.py
    from common.reqMethod import RequestMethod
    from common. ......
    
    
    class Login(unittest.TestCase):
    
        ......
    
        def test_login_success(self):
            ......
    
            # 返回参数断言
            self.assertEqual(self.req_result['code'], get_resp_params(self.sheet, case, 'code'))
            self.assertEqual(self.req_result['msg'], get_resp_params(self.sheet, case, 'msg'))
            self.assertEqual(self.req_result['data']['user_name'], get_resp_params(self.sheet, case, 'user_name'))
            ......
    
    【脚 本 说 明】
    • 通过调用getParams.py模块的get_resp_params方法从Excel获取响应参数,和接口返回的参数做比对,实现断言
    1. 生成测试报告
    • ./zhtyInterfaceTest/runAll.py
    ......
    
     now = time.strftime('%Y-%m-%d %H_%M_%S')  # 获取当前时间
        filename = now + ' testReport.html'
        fp = open('./testResult/{0}'.format(filename), 'wb')
        runner = HTMLTestRunner(stream=fp,
                                title='智慧体育教师版接口测试报告',
                                description='用例执行情况:')
        runner.run(discover)
        fp.close()
    ......
    

    五、最 后

    运行结果
    测试报告截图
    Git地址:https://gitee.com/freedomlidi/zhtyInterfaceTestPublic.git

    OK!

    ~
    ~
    ~

    不积跬步,无以至千里

    积跬步,至千里
  • 相关阅读:
    重构引发的开发思考
    JS-正则表达式
    中文数字转数值
    框架-VuePress(未完)
    HTML-表格
    框架-Vue 2.*的补充
    框架-Vue Class Component tsx 支持(vue-tsx-support V2.2.0)
    CSS-flex
    框架-Vue Class Component 官方支持(vue 2.*、Vue Class Component、vue-property-decorator 9.0.2、vuex-class 0.3.2)
    vue-router:2020-03-26
  • 原文地址:https://www.cnblogs.com/freedomlidi/p/12431302.html
Copyright © 2011-2022 走看看