一、接口依赖

一个接口的测试经常要依赖另外一个或者多个接口成功请求后的返回数据。

例如,当前项目中的充值接口的测试就依赖注册接口,登录接口。

因为需要先注册,在登录然后才充值。

所以进行单接口测试的时候,有些处于业务流中间的接口需要依赖前置接口,可以在测试脚手架中完成这些前置操作。

1. 类级前置条件处理

当一个接口的前置依赖接口只需要在整个测试开始前请求一遍,就可以在类级前置方法setUpClass中去处理即可。

如何将依赖数据传递到后面单元测试方法中?

解决方案

方案有两种:

  1. 全局变量
  2. 类属性
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2021/11/29 21:25
# @Author : shisuiyi
"""
类级前置条件处理
如何把类级前置条件中的数据传递到测试用例函数中
1. 全局变量
2. 类属性(用例类)  用这个
"""

import unittest
import random


# 定义一个全局变量类
class EnvData:
    """
    共享数据
    """
    pass


def do_something():
    return random.randint(0, 9)


class SomeTestCase(unittest.TestCase):

    @classmethod
    def setUpClass(cls) -> None:
        # 执行前置操作
        data = do_something()
        print('前置方法中产生的数据data={}'.format(data))
        # 将data绑定到全局变量类的类属性上
        EnvData.data = data
        # 将data绑定到当前用例类的类属性上
        cls.data = data

    def test_something(self):
        print('执行测试')
        # 获取前置方法中产生的数据
        # 从全局变量中去取
        print('从全局变量中获取到的data={}'.format(EnvData.data))
        # 从当前对象的类上面去取
        print('从当前对象的用例类的类属性上获取data={}'.format(self.__class__.data))


充值接口的测试

1.1 充值接口分析

  1. 接口依赖(前置条件)

    • 注册用户
    • 登录用户
  2. 权限验证

image-1649904631322

1.2 封装注册,登录用户函数

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/12/2 20:09
# @Author  : shisuiyi
# @File    : fixture.py
# @Software: win10 Tensorflow1.13.1 python3.9
import setting
from common import send_http_request
from common import logger


# 封装注册,登录用户函数
def register(mobile_phone, pwd, reg_name=None, _type=None):
    """
    注册用户
    @param mobile_phone: 手机号
    @param pwd:密码
    @param reg_name:注册名
    @param _type:类型 0:管理员,1:普通用户
    """
    # 1. 构造发送注册请求的参数
    data = {
        'mobile_phone': mobile_phone,
        'pwd': pwd,
    }
    if reg_name:
        data['reg_name'] = reg_name
    if _type is not None:
        data['type'] = _type
    # 构造请求头
    headers = {'X-Lemonban-Media-Type': 'lemonban.v1'}
    url = setting.INTERFACES['register']
    # 2.发请求
    try:
        res = send_http_request(url=url, method='post', json=data, headers=headers)
        # 这个是判断请求是否正常
        if res.status_code == 200:
            # 这个是判断响应的结果是否正常
            if res.json()['code'] == 0:
                # 返回登录成功后的数据
                return res.json()['data']
        raise ValueError(res.text)
    except Exception as e:
        logger.warning('用户注册失败')
        raise e


# 定义登录
def login(mobile_phone, pwd):
    """
    登录用户
    :param mobile_phone:
    :param pwd:
    :return: token
    """
    # 1. 构造发送注册请求的参数
    data = {
        'mobile_phone': mobile_phone,
        'pwd': pwd
    }

    # 构造请求头
    headers = {'X-Lemonban-Media-Type': 'lemonban.v2'}
    url = setting.INTERFACES['login']
    # 2.发请求
    try:
        res = send_http_request(url, 'post', json=data, headers=headers)
        # 这个是判断请求是否正常
        if res.status_code == 200:
            # 这个是判断响应的结果是否正常
            if res.json()['code'] == 0:
                # 返回登录成功后的数据
                return res.json()['data']
        raise ValueError(res.text)

    except Exception as e:
        logger.warning('用户登录失败')
        raise e



if __name__ == '__main__':
    from common.data_handler import generate_no_usr_phone
    phone = generate_no_usr_phone()
    res = register(phone, '12345678')
    if res:
        token = login(phone, '12345678')
        print(token)


1.3 项目充值接口用例数据编写

  • 在用例中,需要依赖的数据使用定义槽位的方式。格式#变量名#

1.4 项目充值接口测试用例代码的编写

  • 在类级前置条件中请求依赖接口,并把依赖数据绑定到类属性上

  • 在测试方法中,把指定的依赖数据,替换用例数据中对应的槽位。

 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # @Time    : 2021/12/1 20:21
 # @Author  : shisuiyi
 # @File    : test_recharge.py
 # @Software: win10 Tensorflow1.13.1 python3.9
 import unittest
 import setting
 import json
 from common import logger
 from common.data_handler import get_data_from_excel, generate_no_usr_phone
 from common.fixture import register, login
 from unittestreport import ddt, list_data
 from common import db
 from common.make_requests import send_http_request
 
 cases = get_data_from_excel(setting.TEST_DATA_FILE, 'recharge')
 
 
 @ddt
 class TestRecharge(unittest.TestCase):
     @classmethod
     def setUpClass(cls) -> None:
         logger.info('=======充值接口开始测试=======')
         logger.info('注册用户并登录用户')
         # 1. 注册一个用户
         mobile_phone = generate_no_usr_phone()
         pwd = '12345678'
         register(mobile_phone, pwd)
         # 2. 登录
         data = login(mobile_phone, pwd)
 
         # 3. 保存需要传递到测试函数中的数据到类属性
         # 依赖member_id
         cls.member_id = str(data['id'])
         # 依赖token
         cls.token = data['token_info']['token']
 
     @classmethod
     def tearDownClass(cls) -> None:
         logger.info('=======充值接口结束测试=======')
 
     @list_data(cases)
     def test_recharge(self, item):
         logger.info('>>>>>>>用例{}开始执行>>>>>>>>'.format(item['title']))
         # 1. 处理测试数据
         # 需要替换依赖参数
         item = json.dumps(item)  # 把用例数据dumps成字符串,一次替换
         item = item.replace('#member_id#', self.__class__.member_id)
         item = item.replace('#token#', self.__class__.token)
         item = json.loads(item)
 
         # 再将request_data, expect_data loads为字典
         request_data = json.loads(item['request_data'])
         expect_data = json.loads(item['expect_data'])
 # 略~~

2. 方法级前置条件处理

当一个接口的前置依赖接口在每一次测试前都要请求一遍时,就可以在方法级限制方法setUp中去处理。

import random
import unittest


def do_something():
    return random.random()


class SomeTestCase(unittest.TestCase):

    def setUp(self) -> None:
        # 执行方法级前置
        data = do_something()
        # 从当前用例的对象属性中获取
        self.data = data
        # self.__class__.data = data

    def test1(self):
        print('执行测试1')
        print('获取前置数据:{}'.format(self.data))

    def test2(self):
        print('执行测试1')
        print('获取前置数据:{}'.format(self.data))



if __name__ == '__main__':
    unittest.main()

输出:

执行测试1
获取前置数据:0.21591204964117416
.执行测试1
获取前置数据:0.3895812078440388 
.
----------------------------------------------------------------------
Ran 2 tests in 0.001s

例如:项目审核接口。

如何将依赖数据传递到后面的单元测试方法中?

2.1 解决方案

  • 类属性
  • 对象属性

2.2 审核接口测试

2.2.1 项目审核接口分析

  • 类级前置条件

    • 注册普通用户
    • 登录普通用户
    • 注册管理员用户
    • 登录管理员用户
  • 方法级前置条件

    • 创建项目
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 # @Time    : 2021/12/2 21:28
 # @Author  : shisuiyi
 # @File    : test_audit.py
 # @Software: win10 Tensorflow1.13.1 python3.9
 
 import json
 import unittest
 
 from unittestreport import ddt, list_data
 
 import setting
 from common import logger, db
 from common.data_handler import get_data_from_excel, generate_no_usr_phone
 from common.fixture import register, login, add_loan
 from common.make_requests import send_http_request
 
 cases = get_data_from_excel(setting.TEST_DATA_FILE, 'audit')
 
 
 @ddt
 class TestAudit(unittest.TestCase):
     @classmethod
     def setUpClass(cls) -> None:
         logger.info('===========项目审核接口开始测试===========')
         # 1.注册借钱用户
         mobile_phone = generate_no_usr_phone()
         pwd = '12345678'
         register(mobile_phone, pwd)
 
         # 2.登录借钱用户
         data = login(mobile_phone, pwd)
 
         # 保存投资用户的数据用来创建标,保存在类属性中
         # 要保存借钱用户的id和token
         cls.normal_member_id = data['id']
         cls.normal_token = data['token_info']['token']
         # 3.注册管理员用户
         mobile_phone = generate_no_usr_phone()
         register(mobile_phone, pwd, _type=0)
         # 4.登录管理员用户
         data = login(mobile_phone, pwd)
         # 保存管理员用户的token
         cls.token = data['token_info']['token']
 
     @classmethod
     def tearDownClass(cls) -> None:
         logger.info('===========项目审核接口结束测试===========')
 
     def setUp(self) -> None:
         """
         方法级前置
         :return:
         """
         # 创建项目
         res = add_loan(member_id=self.__class__.normal_member_id,
                        token=self.__class__.normal_token)
 
         # 将创建好的项目的id传递到测试用例中
         # 通过对象属性
         self.loan_id = res['id']

2.2.2 封装创建项目函数

def add_loan(member_id, token, title='借钱实现财富自由', amount=50000,
             loan_rate=12.0, loan_term=11, loan_date_type=2, bidding_days=5):
    """
    添加一个项目
    :param member_id:借款人id
    :param token:借款人token
    :param title:标题
    :param amount:借款金额
    :param loan_rate:年利率
    :param loan_term:借款期限
    :param loan_date_type:借款期限类型
    :param bidding_days:竞标天数
    :return:
    """
    # 构造数据
    data = {
        'member_id': member_id,
        'title': title,
        'amount': amount,
        'loan_rate': loan_rate,
        'loan_term': loan_term,
        'loan_date_type': loan_date_type,
        'bidding_days': bidding_days
    }
    # 构造请求头
    headers = {'X-Lemonban-Media-Type': 'lemonban.v2', 'Authorization': 'Bearer {}'.format(token)}
    url = setting.INTERFACES['add']
    # 2.发请求
    try:
        res = send_http_request(url, 'post', json=data, headers=headers)
        # 这个是判断请求是否正常
        if res.status_code == 200:
            # 这个是判断响应的结果是否正常
            if res.json()['code'] == 0:
                # 返回登录成功后的数据
                return res.json()['data']
        raise ValueError(res.text)

    except Exception as e:
        logger.warning('创建项目失败')
        raise e

2.2.3 项目审核接口用例数据的编写

2.2.4项目审核接口用例编写

   #!/usr/bin/env python
   # -*- coding: utf-8 -*-
   # @Time    : 2021/12/2 21:28
   # @Author  : shisuiyi
   # @File    : test_audit.py
   # @Software: win10 Tensorflow1.13.1 python3.9
   
   import json
   import unittest
   
   from unittestreport import ddt, list_data
   
   import setting
   from common import logger, db
   from common.data_handler import get_data_from_excel, generate_no_usr_phone
   from common.fixture import register, login, add_loan
   from common.make_requests import send_http_request
   
   cases = get_data_from_excel(setting.TEST_DATA_FILE, 'audit')
   
   
   @ddt
   class TestAudit(unittest.TestCase):
       @classmethod
       def setUpClass(cls) -> None:
           logger.info('===========项目审核接口开始测试===========')
           # 1.注册借钱用户
           mobile_phone = generate_no_usr_phone()
           pwd = '12345678'
           register(mobile_phone, pwd)
   
           # 2.登录借钱用户
           data = login(mobile_phone, pwd)
   
           # 保存投资用户的数据用来创建标,保存在类属性中
           # 要保存借钱用户的id和token
           cls.normal_member_id = data['id']
           cls.normal_token = data['token_info']['token']
           # 3.注册管理员用户
           mobile_phone = generate_no_usr_phone()
           register(mobile_phone, pwd, _type=0)
           # 4.登录管理员用户
           data = login(mobile_phone, pwd)
           # 保存管理员用户的token
           cls.token = data['token_info']['token']
   
       @classmethod
       def tearDownClass(cls) -> None:
           logger.info('===========项目审核接口结束测试===========')
   
       def setUp(self) -> None:
           """
           方法级前置
           :return:
           """
           # 创建项目
           res = add_loan(member_id=self.__class__.normal_member_id,
                          token=self.__class__.normal_token)
   
           # 将创建好的项目的id传递到测试用例中
           # 通过对象属性
           self.loan_id = res['id']
   
       @list_data(cases)
       def test_audit(self, item):
           """
           作业
           :param item:
           :return:
           """
           logger.info('>>>>>>>用例{}开始执行>>>>>>>>'.format(item['title']))
           # 1. 处理测试数据
           # 需要替换依赖参数
           item = json.dumps(item)  # 把用例数据dumps成字符串,一次替换
           item = item.replace('#loan_id#', str(self.loan_id))
           item = item.replace('#token#', self.__class__.token)
           item = json.loads(item)
   
           # 再将request_data, expect_data loads为字典
           request_data = json.loads(item['request_data'])
           expect_data = json.loads(item['expect_data'])
   
           # 处理url
           if item['url'].startswith('http'):
               # 是否是全地址
               pass
           elif item['url'].startswith('/'):
               # 是否是短地址
               item['url'] = setting.PROJECT_HOST + item['url']
           else:
               # 接口名称
               item['url'] = setting.INTERFACES[item['url']]
   
           # 2. 测试步骤
           # 发送请求
   
           response = send_http_request(url=item['url'], method=item['method'], **request_data)
   
           # 3. 断言
           # 3.1 断言响应状态码
           try:
               self.assertEqual(item['status_code'], response.status_code)
           except AssertionError as e:
               logger.warning('用例【{}】响应状态码断言异常'.format(item['title']))
               logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(item['title']))
               raise e
           else:
               logger.info('用例【{}】响应状态码断言成功'.format(item['title']))
           # 3.2 断言响应数据
           if item['res_type'].lower() == 'json':
               res = response.json()
           elif item['res_type'].lower() == 'html':
               # 扩展思路
               res = response.text
           try:
               self.assertEqual(expect_data, {'code': res['code'], 'msg': res['msg']})
           except AssertionError as e:
               logger.warning('用例【{}】响应数据断言异常'.format(item['title']))
               logger.warning('用例【{}】期望结果为:{}'.format(item['title'], expect_data))
               logger.warning('用例【{}】的响应结果:{}'.format(item['title'], res))
               logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(item['title']))
               raise e
           else:
               logger.info('用例【{}】响应数据断言成功'.format(item['title']))
   
           # 3.3 数据库断言后面的任务
           if item.get('sql'):  # 返回指定键的值,如果键不在字典中返回默认值 None 或者设置的默认值。
               # 只有sql字段有sql的才需要校验数据库
               try:
                   self.assertTrue(db.exist(item['sql']))
               except AssertionError as e:
                   logger.warning('用例【{}】数据库断言异常,执行的sql为:{}'.format(item['title'], item['sql']))
                   logger.info('<<<<<<<<<用例{}测试结束<<<<<<<'.format(item['title']))
                   raise e
           logger.info('---------------用例{}测试成功---------------'.format(item['title']))
   
   
   if __name__ == '__main__':
       unittest.main()