业务流测试

1.审核接口的分析与代码编写

审核接口使用的是接口关联方法1(也就是所有关联或者依赖的接口全部作为被测接口的前置条件。setUp或setupClass)

审核接口分析

接口名称 请求方法 需要的内容
/member/login(借款人登录)
post
/member/login(管理员登录) post
/loan/add
post
member_id,token(借款人)
/loan/audit   patch  loan_id,token(管理员)

因为要有两个角色的账号登录,所以要添加一个管理员的测试账号

secret_config.py代码修改如下:

# 借款人测试账号
user = {"mobile_phone": "15100002222", "pwd": "12345678"}
# 管理员测试账号
admin_user = {"mobile_phone": "15800002222", "pwd": "12345678"}

base.py代码修改如下:

import requests
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # 我们去访问项目当中接口需要用到的独立的方法或属性
    @classmethod
    def login(cls):
        # 访问登录接口(普通用户登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # 访问登录接口(管理员登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # 访问添加项目的接口
        json_data = {"member_id": "10",
                     "title": "测试项目55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

注意:

  1. 因为添加项目接口用到普通用户登录后的token和member_id,所以调用add_project方法时要传入token和member_id
  2. 添加项目接口记得要设置loan_id属性,提供审核接口使用

 测试用例excel表格内容如下:

 新建test_audit.py代码如下:

import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal

# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')


@ddt
class TestAudit(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # 普通用户登录
        cls.login()
        # 管理员登录
        cls.admin_login()
        cls.add_project(token=cls.token, member_id=cls.member_id)

    def setUp(self) -> None:
        # 添加项目
        # self.add_project(token=self.token, member_id=self.member_id)
        pass

    @data(*excel_data)
    def test_recharge(self, info):
        log.info(f'正在测试{info}')
        # info替换标记的测试数据
        json_data = info['json']
        headers = info['headers']
        # 替换请求体
        if "#loan_id#" in json_data:
            json_data = json_data.replace("#loan_id#", self.loan_id)
        # 替换请求头
        if "#admin_token#" in headers:
            headers = headers.replace("#admin_token#", self.admin_token)
        # 转化成字典类型
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        # 访问充值接口
        recharge_resp = requests.request(url=config.host + info['url'],
                                         method=info['method'],
                                         headers=headers,
                                         json=json_data).json()
        # 断言
        for key, value in expected.items():
            self.assertEqual(value, recharge_resp[key])

 运行结果:

场景一:测试用例共两条,一条成功一条失败(添加项目接口放到setup中)

场景二:测试用例共两条,一条成功/失败一条项目不在审核状态(添加项目接口放到setupClass中)

注意:根据不同的测试用例来决定add_project方法是放在setup中还是setupclass中

正则表达式替换标记的测试数据

修改base.py代码如下:

import requests
import re
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # 我们去访问项目当中接口需要用到的独立的方法或属性
    @classmethod
    def login(cls):
        # 访问登录接口(普通用户登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        # cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # 访问登录接口(管理员登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # 访问添加项目的接口
        json_data = {"member_id": "10",
                     "title": "测试项目55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

    @classmethod
    def replace_data(cls, string):
        # 给指定的字符串替换成动态数据
        result = re.finditer('#(.*?)#', string)
        for i in result:
            # i是匹配到的每个数据
            old = i.group()  # #member_id#
            prop_name = i.group(1)  # member_id
            string = string.replace(old, str(getattr(cls, prop_name)))
        return string

修改test_audit.py代码如下:

import unittest
import requests
import json
from config import config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.db import DBhandler
from common.logger import log
from decimal import Decimal

# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='audit')


@ddt
class TestAudit(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # 普通用户登录
        cls.login()
        # 管理员登录
        cls.admin_login()
        cls.add_project(token=cls.token, member_id=cls.member_id)

    def setUp(self) -> None:
        # 添加项目
        # self.add_project(token=self.token, member_id=self.member_id)
        pass

    @data(*excel_data)
    def test_recharge(self, info):
        log.info(f'正在测试{info}')
        # 使用正则表达式替换json_data,headers中#  #标记内容
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # 转化成字典类型
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        # 访问充值接口
        recharge_resp = requests.request(url=config.host + info['url'],
                                         method=info['method'],
                                         headers=headers,
                                         json=json_data).json()
        # 断言
        for key, value in expected.items():
            self.assertEqual(value, recharge_resp[key])

运行结果:

 总结:

  1. 在使用正则表达式APICase下的replace_data这个方法时,要进行两次替换,json_data和headers,因为请求头和请求体内都有标记
  2. 调用replace_data之前,必须在APICase设置同名属性,这里设置的是loan_id,admin_token

2.投资接口的分析与代码编写

投资接口使用的是接口关联方法2(业务流的测试方法,不需要做封装各种所需的接口做为前置条件)

投资接口分析

接口名称 请求方法
/member/login(投资人登录)
post
/member/login(借款人登录) post
/member/login(管理员登录) post
/loan/add  post  
/loan/audit
patch
/member/invest
  post 

方法二是不需要依赖其他接口的,因此所需要的接口都会写到测试用例中

编写测试用例注意点:

  1. 单词一定注意别写错了,如investor_phon等
  2. 投资金额为空时,amount整个字段就可以不写,不用写成"amount":
  3. extractor是数据提取,为下一个接口需要做准备,当登录投资人接口时,拿到investor_member_id和investor_token;当登录借款人接口时,拿到loan_member_id和loan_token;当登录投资人接口时,拿到investor_member_id,loan_token;当登录管理员接口时,拿到admin_member_id和admin_token;当访问添加项目接口时,拿到loan_id

接着就开始编写代码了,因为我们涉及到三个角色,所以需要不同角色的账号,我这里把投资人跟借款人作为一个测试账号登录,所以secret_config.py的代码不需要修改,如果是三个账号登录,那就再增加一个。base.py模块的内容也不需要修改,只需要调用replace_data方法即可

测试投资接口代码如下:

import unittest
import requests
import json
from jsonpath import jsonpath
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log

# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')


@ddt
class TestInvest(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # 直接从配置文件中读取需要的数据,然后设置成APICase属性
        cls.investor_phone = secret_config.user['mobile_phone']
        cls.investor_pwd = secret_config.user['pwd']
        cls.loan_phone = secret_config.user['mobile_phone']
        cls.loan_pwd = secret_config.user['pwd']
        cls.admin_phone = secret_config.admin_user['mobile_phone']
        cls.admin_pwd = secret_config.admin_user['pwd']

    @data(*excel_data)
    def test_invest(self, info):

        # 1.对数据进行预处理,接口访问,数据替换,字符串转化成字典
        log.info(f'正在测试{info}')
        # 使用正则表达式替换json_data,headers中#  #标记内容
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # 转化成字典类型
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        extractor = {}
        if info['extractor']:
            extractor = json.loads(info['extractor'])
        # 2.访问接口,得到接口的返回结果
        resp = requests.request(url=config.host + info['url'],
                                method=info['method'],
                                headers=headers,
                                json=json_data).json()
        # 3.响应结果当中读取接下来接口需要的数据,设置成##标记同名属性如investor_token
        # 获取投资人登录的id和token
        # 把id设置成AIPCase.investor_member_id
        # 把token设置成AIPCase.investor_token
        # 通过jsonpath获取$..id,$..token
        # 设置属性,属性名称也要写到excel表格里面,如investor_member_id
        for prcp_name, jsonpath_expression in extractor.items():
            value = jsonpath(resp, jsonpath_expression)[0]
            # 设置类属性
            setattr(APICase, prcp_name, value)
        # 4.断言
        for key, value in expected.items():
            self.assertEqual(value, resp[key])

运行结果:

注意:因为replace_data封装成了类方法,所以要替换的数据要写到类属性,也就是写到setupclass中,因为类方法不可以调用实例方法(把replace_data设置成实例方法时,要替换的数据就可以放到setup中了)

接着我们来继续把代码进行封装,把数据替换的代码封装到prev_data方法中,把访问接口封装到visit方法中,把数据提取封装到extract方法中,把断言封装到assert_all方法中

修改base.py代码如下:

import requests
import re
import json
from config import config, secret_config
from jsonpath import jsonpath


class APICase:
    # 我们去访问项目当中接口需要用到的独立的方法或属性
    @classmethod
    def login(cls):
        # 访问登录接口(普通用户登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.token = jsonpath(login_resp, '$..token')[0]
        # cls.before_money = jsonpath(login_resp, '$..leave_amount')[0]
        return (cls.member_id, cls.token)

    @classmethod
    def admin_login(cls):
        # 访问登录接口(管理员登录)
        headers = {"X-Lemonban-Media-Type": "lemonban.v2"}
        json_data = secret_config.admin_user
        login_resp = requests.request(url=config.host + 'member/login',
                                      method='post',
                                      headers=headers,
                                      json=json_data).json()
        cls.admin_member_id = str(jsonpath(login_resp, '$..id')[0])
        cls.admin_token = jsonpath(login_resp, '$..token')[0]
        return (cls.admin_member_id, cls.admin_token)

    @classmethod
    def add_project(cls, token, member_id):
        # 访问添加项目的接口
        json_data = {"member_id": "10",
                     "title": "测试项目55",
                     "amount": 100000,
                     "loan_rate": 10.0,
                     "loan_date_type": 2,
                     "loan_term": 30,
                     "bidding_days": 5}

        headers = {"X-Lemonban-Media-Type": "lemonban.v2",
                   "Authorization": f"Bearer {token}"}
        add_resp = requests.request(url=config.host + '/loan/add',
                                    method='post',
                                    headers=headers,
                                    json=json_data).json()
        cls.loan_id = str(jsonpath(add_resp, '$..id')[0])
        return add_resp

    @classmethod
    def replace_data(cls, string):
        # 给指定的字符串替换成动态数据
        result = re.finditer('#(.*?)#', string)
        for i in result:
            # i是匹配到的每个数据
            old = i.group()  # #member_id#
            prop_name = i.group(1)  # member_id
            string = string.replace(old, str(getattr(cls, prop_name)))
        return string

    def prev_data(self, info):
        # 数据替换
        # 使用正则表达式替换json_data,headers中#  #标记内容
        json_data = info['json']
        headers = info['headers']
        json_data = self.replace_data(json_data)
        headers = self.replace_data(headers)
        # 转化成字典类型
        json_data = json.loads(json_data)
        headers = json.loads(headers)
        expected = json.loads(info['expected'])
        extractor = {}
        if info['extractor']:
            extractor = json.loads(info['extractor'])
        # 将替换和转化之后的数据重新设置回字典的key,value
        info['json'] = json_data
        info['headers'] = headers
        info['expected'] = expected
        info['extractor'] = extractor
        return info

    def visit(self, info):
        # 访问接口
        resp = requests.request(url=config.host + info['url'],
                                method=info['method'],
                                headers=info['headers'],
                                json=info['json']).json()
        return resp

    @classmethod
    def extract(cls, info, resp):
        # 数据提取
        for prcp_name, jsonpath_expression in info['extractor'].items():
            value = jsonpath(resp, jsonpath_expression)[0]
            # 设置类属性
            setattr(cls, prcp_name, value)

    def assert_all(self, info, resp):
        # 断言
        for key, value in info['expected'].items():
            self.assertEqual(value, resp[key])

    def steps(self, info):
        info = self.prev_data(info)
        resp = self.visit(info)
        self.extract(info, resp)
        self.assert_all(info, resp)

修改投资接口代码如下:

import unittest
from config import secret_config, config
from ddt import ddt, data
from common.excel import read_excel
from common.base import APICase
from common.logger import log

# 得到测试数据
excel_data = read_excel(file_name=config.cases_dir, sheet_name='invest')


@ddt
class TestInvest(unittest.TestCase, APICase):
    @classmethod
    def setUpClass(cls) -> None:
        # 直接从配置文件中读取需要的数据,然后设置成APICase属性
        cls.investor_phone = secret_config.user['mobile_phone']
        cls.investor_pwd = secret_config.user['pwd']
        cls.loan_phone = secret_config.user['mobile_phone']
        cls.loan_pwd = secret_config.user['pwd']
        cls.admin_phone = secret_config.admin_user['mobile_phone']
        cls.admin_pwd = secret_config.admin_user['pwd']

    @data(*excel_data)
    def test_invest(self, info):
        log.info(f'正在测试{info}')
        self.steps(info)

运行结果:

总结:

什么时候用方法1,什么时候用方法2

方法1:setup操作,封装,每个具体的方法封装成函数,适合接口数据比较少,不嫌麻烦

方法2:适合接口数量较多,逼格高,实现难度大,通用的处理流程,调试起来比较麻烦

自动化测试需要注意的知识点:

  • 读取数据库,读取excel,读取yaml,log日志
  • 测试:单元测试框unittest/pytest
  • 自动化测试的思想:ddt数据驱动,参数化
  • 前面三个点几乎可以做任何的自动化测试任务(setup可以做接口依赖)
  • 数据伪造,数据生成,faker,手机号码
  • 接口关联
  1. 方法1:setup  #数据替换#,if 或 正则表达式
  2. 方法2:数据提取,放在excel

おすすめ

転載: blog.csdn.net/weixin_40611700/article/details/121159399