接口自动化实战之审核接口

接口介绍

在这里插入图片描述

excel测试用例

在这里插入图片描述

代码实操

先跑起来!

import unittest
import ddt
from middleware.handler import Handler

# 初始化
logger = Handler.logger
test_data = Handler.excel.read_data("add")


@ddt.ddt
class LoginTestCase(unittest.TestCase):

    @ddt.data(*test_data)
    def test_add(self, test_info):
        data = test_info["data"]
        print(data)

问题1:接口依赖

首先看下接口的业务描述,可以看出这里需要管理员的账号进行登录后才能进行审核操作,所以依赖了一个登录接口:
在这里插入图片描述
另外,请求参数中有一个loan_id,表明至少得有一个项目才能进行审核,事先得准备一个项目,所以又依赖了一个添加项目的接口:
在这里插入图片描述
而除了管理员登录后获取管理员的id及token之外,添加项目也需要普通用户进行登录才能进行添加,所以需要两个角色都进行登录。
两个角色的用户登录操作可以放到setUpClass中,因为只需要登录一次,就能一直进行添加项目及审核项目。那么添加项目的操作是放到setUp中呢还是setUpClass中呢?答案是setUp,也就是说每一条用例都需要重新生成一个新的项目。这是为什么呢?
我们可以看回excel中的数据。第一条用例生成的项目审核通过后,loan表中的status变为2(竞标中),而第二条用例不新生成一个,继续拿第一个竞标中状态的项目审核是审核不了的;同样第三个用例也是。所以这里需要放到setUp中,以确保每一个测试用例都能够生成一个新的项目进行审核。
在这里插入图片描述

问题2:登录、添加项目的前置处理

登录时需要普通用户、管理员用户进行登录。那么这两个角色登录后,我们需要获取什么呢?

  • 管理员的token(headers中需要)
  • 普通用户的token(headers中需要)
  • 普通用户的id(添加项目时的输入参数:member_id)
    所以我们可以把管理员的登录及普通用户的登录放到handler中并且获取这三个值,然后在setUpClass中调用handler来获取这三个值。
    那么handler中需要分别写普通用户的登录方法及管理员的登录方法吗?不需要。我们只需要一个登录方法就够了,然后这个登录方法最后返回token及id,然后再写几个方法来获取这两个值。
    那么如何分辨获取的token是普通用户的token还是管理员的token呢?我们可以分别把管理员的账号及普通用户的账号放到yaml文件中,然后登录时传入这个账号,接着获取token的方法中统一调用登录方法,只不过传入的参数不同。
    对于添加项目来说,我们也可以直接写到handler中去。
    代码如下:
  • Handler.py
class Handler():
    #通过get_token方法获取login方法中返回结果中的token的值
    #获取普通用户的token
    @property
    def get_token(self):
        user = self.login(self.yaml["user"])
        return user["token"]
    #获取管理员用户的token
    @property
    def get_admin_token(self):
        admin_user = self.login(self.yaml["admin_user"])
        return admin_user["token"]
    # 通过get_userId方法获取login方法中返回结果中的user_id的值
    @property
    def get_userId(self):
        user = self.login(self.yaml["user"])
        return user["user_id"]

    #通过get_loanId方法获取add_loan方法中的返回值
    @property
    def get_loanId(self):
        loan = self.add_loan()
        return loan

    #由于多个接口都需要依赖登录接口来登录,所以这里准备登录的接口请求
    def login(self,user):
        #通过request_handler请求登录接口,其中url可以从yaml中获取host再拼接上登录的url,json可以直接取yaml中的user配置项
        req = request_handler.requests_handler(
            url = self.yaml["host"] + "/member/login",
            method = "post",
            headers = {"X-Lemonban-Media-Type": "lemonban.v2 "},
            json = user
        )
        #获取返回结果中的token。由于token_info中的token_type和token的内容是分开的,所以需要拼接起来。另外需要注意取值时的层级关系
        token_type = jsonpath(req,"$..token_type")[0]
        token_content = jsonpath(req,"$..token")[0]
        token = " ".join([token_type, token_content])
        #获取返回结果中的用户id,因为充值接口需要用到member_id
        user_id = jsonpath(req,"$..id")[0]
        #返回token及user_id
        return {"token":token,"user_id":user_id}

    #由于其他有些接口需要依赖添加项目的接口,所以在这里准备添加项目的接口请求方法
    def add_loan(self):
        #准备headers
        token = self.get_token
        headers = {"X-Lemonban-Media-Type":"lemonban.v2","Authorization":token}
        #准备url
        url = self.yaml["host"]+"/loan/add"
        #准备请求参数
        data = {"member_id":1,"title":"买波音757","amount":2000,"loan_rate":12.0,"loan_term":3,"loan_date_type":1,"bidding_days":5}
        #调用request_handler请求add接口
        req = request_handler.requests_handler(
            url = url,
            method = "post",
            headers = headers,
            json = data
        )
        #获取返回结果中的id的值
        loan_id = jsonpath(req,"$..id")[0]
        return loan_id

接着在测试类中,直接在前置条件中调用handler中的getXXX函数来获取相应的属性即可:

  • test_audit.py
    #通过前置类方法获取Handler中的loan_id及admin_token
    @classmethod
    def setUpClass(cls) -> None:
        #cls.loan_id = env_data.get_loanId
        cls.admin_token = env_data.get_admin_token

    #通过前置实例方法初始化mysql的连接对象,使得每个用例用到的连接对象都是相互独立的
    def setUp(self) -> None:
        self.loan_id = env_data.get_loanId
        self.db = env_data.mysql_class()

问题3:特殊用例——审核的项目不存在

看下excel中的最后一条用例,这里需要审核一个不存在的项目,我们直接拿一个新生成的项目id加上10000就成了一个不存在的项目。那么在python读取这条excel数据时,是用json.loads()呢还是eval()呢?答案是eval,因为这里需要执行字符串里面的运算,而json.loads()只会把字符串转换成字典,loan_id的值还是#loan_id#+10000。
在这里插入图片描述
这里总结一下:当excel中的数据全是json格式的数据,则可以使用json.loads()转换成字典;而如果excel中的数据需要进行运算,或者可以完全转换成字典,则可以使用eval。

问题4:特殊用例——审核不在待审核状态

当我们生成一个新的项目时,那么它的状态就是待审核状态。那么现在要找一个不在待审核状态的load_id,有什么办法吗?

方法一:在成功用例的断言中存储一条记录

一个项目审核通过后,状态就变为2(竞标中),符合不在待审核状态的条件。而在进行成功用例的数据库断言中:

        #数据库表断言
        if actual_result["code"] == 0:
            #根据上面的loan_id查询loan表中对应记录的status的值
            loan_in_sql = self.db.query("SELECT * FROM futureloan.loan WHERE id={}".format(self.loan_id))
            #拿loan表中status的值与预期结果的status的值进行断言
            self.assertTrue(loan_in_sql["status"] == expect_result["status"])
            

如果断言能够通过,则证明这个loan_id是审核通过的,也就可以利用了。这时我们可以通过一个变量来保存这个loan_id,然后在执行到审核不在待审核状态项目的测试用例中再传入即可:

        if "#pass_loan_id#" in data:
            data = data.replace("#pass_loan_id#", str(env_data.pass_loan_id)

# 省略中间代码

        if resp["code"] == 0:
            # loan_id = 8, status = 2
            loan = self.db.query("SELECT * FROM futureloan.loan WHERE id={};".format(self.loan_id))
            self.assertEqual(loan["status"], expected["status"])
			#用一个临时变量存储loan_id
            env_data.pass_loan_id = loan["id"]

方法二:在loan表中捞一条status!=1的数据

这个很容易理解,就不展开说了,直接上代码:

        if "#pass_loan_id#" in data:
            pass_loan = self.db.query("SELECT * FROM futureloan.loan WHERE status !=1;")
            data = data.replace("#pass_loan_id#", str(pass_loan["id"]))

#省略后面的代码

问题5:审核是否成功的断言

跟以往测试用例不同的是,这里我们虽然可以通过返回码来判断接口是否执行成功,但无法通过接口的返回结果来判断业务上用例是否通过,因为没有哪一个字段来表明审核之后的结果:
在这里插入图片描述
所以我们只有通过查询loan表中的status字段来判断是否审核成功:

由表中可以看出,当新增了一个项目时,项目状态为审核中;当approved_or_not设为true时,则项目状态为竞标中;当approved_or_not设为false时,则项目状态为审核不通过。所以当status=2时,才证明审核通过。

代码调试

from middleware.Handler import Handler
from common import request_handler
import ddt
import json
import unittest

env_data = Handler()
#初始化Handler中的yaml_handler
yaml = Handler.yaml
#初始化Handler中的excel_handler
excel = Handler.excel
#厨师化Handler中的logging_handler
log = Handler.logger
#通过excel_handler获取excel中audit表单中的所有数据
excel_data = excel.get_data("audit")

#在需要用到数据驱动的类上定义ddt装饰器
@ddt.ddt
class Test_audit(unittest.TestCase):
    #通过前置类方法获取Handler中的loan_id及admin_token
    @classmethod
    def setUpClass(cls) -> None:
        cls.loan_id = env_data.get_loanId
        cls.admin_token = env_data.get_admin_token

    #通过前置实例方法初始化mysql的连接对象,使得每个用例用到的连接对象都是相互独立的
    def setUp(self) -> None:
        self.db = env_data.mysql_class()

#在需要用到数据驱动的类方法上定义data装饰器
    @ddt.data(*excel_data)
    def test_audit(self,cases):
        #准备excel中的data并用excel_data变量接收
        excel_data = cases["data"]
        # 准备excel中的headers并用excel_headers变量接收
        excel_headers = cases["headers"]
        #替换data中的#loan_id#
        if "#loan_id#" in excel_data:
            excel_data = excel_data.replace("#loan_id#",self.loan_id)
        #替换data中的#pass_loan_id#
        if "#pass_loan_id#" in excel_data:
            #查询loan表中状态为还款中的记录并取其中一条记录的id替换到#pass_loan_id#
            pass_loan_id = self.db.query("SELECT * FROM futureloan.loan WHERE status=3")["id"]
            excel_data = excel_data.replace("#pass_loan_id#",pass_loan_id)
        # 替换headers中的#admin_token#
        if "#admin_token#" in excel_headers:
            excel_headers = excel_headers.replace("#admin_token#",self.admin_token)
        #将excel中字符串格式的data转换成字典格式
        data = eval(excel_data)
        #调用request_handler请求接口
        actual_result = request_handler.requests_handler(
            method = "post",
            url = yaml["host"] + cases[url],
            json = data,
            headers = json.loads(excel_headers)
        )
        #准备预期结果
        expect_result = json.loads(cases["expected"])
        #将预期结果与实际结果进行断言
        self.assertEqual(expect_result["code"],actual_result["code"])
        self.assertEqual(expect_result["msg"],actual_result["msg"])
        #数据库表断言
        if actual_result["code"] == 0:
            #根据上面的loan_id查询loan表中对应记录的status的值
            loan_in_sql = self.db.query("SELECT * FROM futureloan.loan WHERE id=".format(self.loan_id))
            #拿loan表中status的值与预期结果的status的值进行断言
            self.assertTrue(loan_in_sql["status"] == expect_result["status"])

BUG1:TypeError

在这里插入图片描述
出错代码如下:

 #替换data中的#loan_id#
        if "#loan_id#" in excel_data:
            excel_data = excel_data.replace("#loan_id#",self.loan_id)

类型转化错误,self.loan_id获得的loan_id为int类型,而替换到excel中的单元格时需要存入字符串类型,所以得通过str()方法把int类型转换成字符串类型。

BUG2:AssertionError

1005 != 0

Expected :0
Actual   :1005
<Click to see difference>

Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1\plugins\python-ce\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equals
    old(self, first, second, msg)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 912, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 905, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 0 != 1005

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
    yield
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 676, in run
    self._callTestMethod(testMethod)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
    method()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\ddt.py", line 182, in wrapper
    return func(self, *args, **kwargs)
  File "C:\Users\rainstar\PycharmProjects\pyproject\API_project_v0\tests\test_audit.py", line 60, in test_audit
    self.assertEqual(expect_result["code"],actual_result["code"])

这里我们查到实际结果的状态码为1005,查看接口文档:
在这里插入图片描述
可得知这是服务器繁忙,我们可以尝试用postman进行接口访问:
在这里插入图片描述
可看到返回的结果依然为1005,而错误信息是:Request method ‘POST’ not supported,也就是post的请求方法是不支持的,这时我们可以看下接口文档:
在这里插入图片描述
可看到请求方法为patch,我们把postman的请求方法改成patch即可访问成功:
在这里插入图片描述
于是我们把代码中request_handler的请求方法改成patch,错误就没了。

BUG3:pymysql.err.ProgrammingError


Error
Traceback (most recent call last):
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
    yield
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 676, in run
    self._callTestMethod(testMethod)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
    method()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\ddt.py", line 182, in wrapper
    return func(self, *args, **kwargs)
  File "C:\Users\rainstar\PycharmProjects\pyproject\API_project_v0\tests\test_audit.py", line 65, in test_audit
    loan_in_sql = self.db.query("SELECT * FROM futureloan.loan WHERE id=".format(self.loan_id))
  File "C:\Users\rainstar\PycharmProjects\pyproject\API_project_v0\common\sql_handler.py", line 30, in query
    self.cursor.execute(sql)
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\cursors.py", line 163, in execute
    result = self._query(query)
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\cursors.py", line 321, in _query
    conn.query(q)
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\connections.py", line 505, in query
    self._affected_rows = self._read_query_result(unbuffered=unbuffered)
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\connections.py", line 724, in _read_query_result
    result.read()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\connections.py", line 1069, in read
    first_packet = self.connection._read_packet()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\connections.py", line 676, in _read_packet
    packet.raise_for_error()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\protocol.py", line 223, in raise_for_error
    err.raise_mysql_exception(self._data)
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\pymysql\err.py", line 107, in raise_mysql_exception
    raise errorclass(errno, errval)
pymysql.err.ProgrammingError: (1064, "You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '' at line 1")


错误是可能sql写的有问题,对应的代码如下:

            #根据上面的loan_id查询loan表中对应记录的status的值
            loan_in_sql = self.db.query("SELECT * FROM futureloan.loan WHERE id=".format(self.loan_id))
   

这里id=后面忘记写{}了,导致系统判断sql写错了。

BUG4:AssertionError

2 != 0

Expected :0
Actual   :2
<Click to see difference>

Traceback (most recent call last):
  File "C:\Program Files\JetBrains\PyCharm Community Edition 2020.1\plugins\python-ce\helpers\pycharm\teamcity\diff_tools.py", line 32, in _patched_equals
    old(self, first, second, msg)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 912, in assertEqual
    assertion_func(first, second, msg=msg)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 905, in _baseAssertEqual
    raise self.failureException(msg)
AssertionError: 0 != 2

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 60, in testPartExecutor
    yield
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 676, in run
    self._callTestMethod(testMethod)
  File "C:\Users\rainstar\AppData\Local\Programs\Python\Python38\lib\unittest\case.py", line 633, in _callTestMethod
    method()
  File "C:\Users\rainstar\PycharmProjects\pyproject\venv\lib\site-packages\ddt.py", line 182, in wrapper
    return func(self, *args, **kwargs)
  File "C:\Users\rainstar\PycharmProjects\pyproject\API_project_v0\tests\test_audit.py", line 60, in test_audit
    self.assertEqual(expect_result["code"],actual_result["code"])

可看到实际结果返回的状态码为2,我们看下接口文档2代表什么:
在这里插入图片描述
可看到2表示参数错误,所以有可能是request_handler的传参有问题。

最终代码

from middleware.Handler import Handler
from common import request_handler
import ddt
import json
import unittest

env_data = Handler()
#初始化Handler中的yaml_handler
yaml = Handler.yaml
#初始化Handler中的excel_handler
excel = Handler.excel
#厨师化Handler中的logging_handler
log = Handler.logger
#通过excel_handler获取excel中audit表单中的所有数据
excel_data = excel.get_data("audit")

#在需要用到数据驱动的类上定义ddt装饰器
@ddt.ddt
class Test_audit(unittest.TestCase):
    #通过前置类方法获取Handler中的loan_id及admin_token
    @classmethod
    def setUpClass(cls) -> None:
        cls.token = env_data.get_token
        cls.admin_token = env_data.get_admin_token
        cls.member_id = env_data.get_userId

    #通过前置实例方法初始化mysql的连接对象,使得每个用例用到的连接对象都是相互独立的
    def setUp(self) -> None:
        self.loan_id = env_data.get_loanId
        self.db = env_data.mysql_class()

#在需要用到数据驱动的类方法上定义data装饰器
    @ddt.data(*excel_data)
    def test_audit(self,cases):
        #准备excel中的data并用excel_data变量接收
        excel_data = cases["data"]
        # 准备excel中的headers并用excel_headers变量接收
        excel_headers = cases["headers"]
        #替换data中的#loan_id#
        if "#loan_id#" in excel_data:
            excel_data = excel_data.replace("#loan_id#",str(self.loan_id))
        #替换data中的#pass_loan_id#
        if "#pass_loan_id#" in excel_data:
            #查询loan表中状态为还款中的记录并取其中一条记录的id替换到#pass_loan_id#
            pass_loan_id = self.db.query("SELECT * FROM futureloan.loan WHERE status=3")["id"]
            excel_data = excel_data.replace("#pass_loan_id#",str(pass_loan_id))
        # 替换headers中的#admin_token#
        if "#admin_token#" in excel_headers:
            excel_headers = excel_headers.replace("#admin_token#",str(self.admin_token))
        #将excel中字符串格式的data转换成字典格式
        data = eval(excel_data)
        #调用request_handler请求接口
        actual_result = request_handler.requests_handler(
            method = "patch",
            url = yaml["host"] + cases["url"],
            json = data,
            headers = json.loads(excel_headers)
        )
        #准备预期结果
        expect_result = json.loads(cases["expected"])
        #将预期结果与实际结果进行断言
        self.assertEqual(expect_result["code"],actual_result["code"])
        self.assertEqual(expect_result["msg"],actual_result["msg"])
        #数据库表断言
        if actual_result["code"] == 0:
            #根据上面的loan_id查询loan表中对应记录的status的值
            loan_in_sql = self.db.query("SELECT * FROM futureloan.loan WHERE id={}".format(self.loan_id))
            #拿loan表中status的值与预期结果的status的值进行断言
            self.assertTrue(loan_in_sql["status"] == expect_result["status"])

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值