谈下接口测试

·  阅读 167
谈下接口测试

一.接口测试步骤:

1、阅读测试接口文档,检验接口文档的完整性、正确性、一致性、易理解性和易浏览性。

2、编写测试用例\这个大家都熟,根据接口文档编写测试用例。用例编写方法可以按照黑盒测试的用例编写规则来编写,如:边界值、正交表等等设计方法。

3、根据测试用例进行API的手工执行测试,根据用例执行测试,注意验证预期结果,执行结束后出具测试报告。

而实际是,需求来了,开发提测了,然后就没然后了,测试盯着那个转过来的一句话需求,百感交集。算了.....下班了。 偶尔碰到大哥的会帮你写一写接口相关的注意事项,例如配置项是什么、要注意什么、日志在哪儿等等。

我相信大部分测试都会遇到,这时候我们不要慌,先把需求搞清楚,实在搞不清楚去问一问,咋用的,问下产品你要的是什么样儿的。

当你搞清楚之后。就可以来写接口测试用例了,不知道怎么写 我给你举个例子。

image.png

写完之后,去用例评审一下,说下我们测试思路等等。过去吹吹牛皮。至于为啥要写这个文档,第一有个记录,留下证据,哈哈哈

这些搞完之后,我们就可以开始测试了,至于用postman 或者jmeter或者python、java的 都行 ,看个人意愿,当项目组中要求我们写接口自动化的时候 ,就可以python 搞起来了。

二、这里分享两种方法:

1.参数更新法


class SendRequest(object):
    def __init__(self):

        self.header = {
            "Content-Type": "application/json",
            "accessToken": "我的token 不给你"
        }

    def test_get_weather(self, new_data={}):
        """
        获取天气
        :return:
        """
        url = HOST + '/v1/tclplus/weather'

        body = {
            "city": "深圳市"
        }
        args = updata_args(body, new_data)
        print(f"请求参数为{args}")
        res = requests.get(url=url, params=args, headers=self.header)
        return res
复制代码

如上表格中,我们写好的用例,先写一个发起请求的主函数、然后通过update_args这个方法来更新参数。实现用例参数更新。

def updata_args(data_args, new_args, **kwargs):
    """

    :rtype: object
    """
    if kwargs:
        data_args.update(kwargs)
    if new_args:
        data_args.update(new_args)
    return data_args
复制代码

记住两个点,更新参数动作,函数最后需要return 这时候我们就可以来写我们的用例了,用例可以根据unittest框架来

import unittest

from case import SendRequest


class Rampage(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        pass

    @classmethod
    def tearDownClass(cls):
        pass

    def test_get_weather_1(self):
        result = SendRequest().test_get_weather({"city": "上海市"})
        self.assertEqual(200, result.status_code, f"fail{result.status_code}")
        self.assertEqual("200", result.json().get("code"))

    def test_get_weather_2(self):
        result = SendRequest().test_get_weather({"city": ""})
        self.assertEqual(200, result.status_code, f"fail{result.status_code}")
        self.assertEqual(str(500), result.json().get("code"))
复制代码

这是常规的我们python写接口自动化所用的办法,当然细心的妙蛙种子们会发现,用例存在很多一样的。这时候可以换一种方式,采用ddt

import unittest

from ddt import ddt, data, unpack
from case import SendRequest


test_data = ((1, 2, 3), (4, 5, 6), (7, 8, 9))


@ddt
class Case(unittest.TestCase):
    @data({"city": "深圳市"}, {"city": "北京市"}, {"city": "南京市"}, {"city": "东莞市"})
    def test_case_(self, a):
        result = SendRequest().test_get_weather(a)
        print(result.status_code, result.text)
复制代码

ddt写好,看起来是一条用例,实际运行时,请求了四次,分别是不同的参数。

2.第二种方法,文档填下第一

看起来,听起来很高大上。实际上,是将用例表格写好,然后读取表格数据、发起请求、结果校验都帮你一条龙服务了。

先来读取数据: Getexcel.py

# -*-coding:utf-8 -*-
import json
import logging
import os.path
import requests
import xlrd
import urllib3 as urllib3

from common import conf

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# normpath 代表绝对路径,而dirname为当前路径
excel_path = os.path.dirname(__file__) + "/Iotdata.xls"


class Excel(object):
    # 定义类两个初始化变量
    def __init__(self, tableName, caseName):
        self.excel_path = excel_path
        self.table_name = tableName
        self.case_name = caseName

    def _get_table(self):
        """获取表格名称"""
        excel = xlrd.open_workbook(self.excel_path)
        sheets_name = excel.sheet_names()
        if self.table_name in sheets_name:
            table = excel.sheet_by_name(self.table_name)
            return table
        else:
            logging.error(f"sheet {self.table_name} not found in {self.excel_path}")
            raise ValueError(f"sheet {self.table_name} not found in {self.excel_path}")

    def get_content(self):
        """
        获取指定用例的数据
        @return:
        """
        table_name = self._get_table()  # 得到表格名称
        line_num = table_name.nrows
        is_found_case = False
        result = ""
        if line_num < 2:
            logging.error("the sheet content is null!")
            raise ValueError("the sheet content is null!")
        else:
            for i in range(1, line_num):
                line = table_name.row_values(i)
                if line == "":
                    pass
                elif self.case_name == line[0]:  # 第一列 0代表
                    result = line
                    logging.debug(f"find data: {result}")  # 找到了用例名称
                    is_found_case = True
                    break
        if is_found_case:
            return result
        else:
            logging.error(f"caseName: {self.case_name} not found!")  # 没有找到用例名称
            raise ValueError(f"caseName: {self.case_name} not found!")

    def __getstate__(self):
        """获取acctoken  必须返回一个字典"""
        # res = requests.post(url=conf.url, data=conf.rsa_data, headers=conf.header, verify=False)
        # accessToken = res.json()["accessToken"]
        # header = {
        #     "Content-Type": "application/json",
        #     "accessToken": accessToken
        # }
        # return header
        header = json.loads(open("./TOKEN", 'r').read())
        return header
复制代码

发起请求的py sendrequests.py

# -*-coding:utf-8 -*-
import functools
import inspect
import json
import logging
from collections import namedtuple
import platform
import os
from datetime import datetime

import urllib3

from common import CheckResult
from common.Getexcel import Excel
import requests

urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

# 表格文件的抬头
col_name = ["caseName", "scheme", "host", "path", "method", "type", "data", "expect", "note", "step", "action"]
col_obj = namedtuple("col", col_name)
col = col_obj(*(i for i in range(len(col_name))))


def _excel_data(tableName, caseName):
    """
    表格的数据处理
    @return:
    """
    excel_obj = Excel(tableName, caseName)
    data = excel_obj.get_content()
    url = (data[col.scheme].lower() + "://" + data[col.host] + data[col.path])  # 组装为完整的url
    body_type = data[col.type]  # 根据获得的type来获取不同的请求头
    body = data[col.data]  # 获取请求参数
    expect_str = data[col.expect]  # 获取期望结果
    step = data[col.step].strip() != ""  # 用例描述
    return url, body, expect_str, body_type, step


def request_data(tableName, caseName):
    """
    发起请求
    @return:
    """
    url, body, expect_str, body_type, step = _excel_data(tableName, caseName)
    try:
        if "open(" in body:  # 判断是否为文件读取
            body1 = body[body.rfind('open('):]
            mater_name = body1[9:body1.find(',') - 1]
            if platform.system() == "Windows":
                mater_path = os.path.abspath(
                    os.path.join(os.path.dirname(os.path.dirname(__file__)), mater_name)).replace('\', '\\')
            else:
                mater_path = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), mater_name))
            mater_name2 = body1[6:body1.find(',') - 1]
            body = body.replace(mater_name2, mater_path)
        exec("body = " + body)  # 转换为字典
    except Exception as e:
        logging.warning(f"body: {body} exec to dict failed!  --- error:{str(e)}")
    header = {}
    if body_type.title() == "file":
        header.pop("Content-Type")
    elif body_type.title() == "Json":
        header["Content-Type"] = "application/json;charset=utf-8"
        body = json.dumps(body, ensure_ascii=False)
    elif body_type.title() == "Data":
        header["Content-Type"] = "text/plain;charset=UTF-8"
    elif body_type.title() == "backs":  # 如果是后台管理系统,其中ubtoken需要自己更新
        header = {"Content-Type": "application/json", 'ubtoken': 'fgoBYK7jTnGoDlyVC36PVyk2ZcDTVkyz'}
    elif body_type == "from":
        header = Excel(tableName, caseName).__getstate__()
    # print(f"请求头为:{header}\n请求body为:{body}\n请求地址为:{url}\n期望结果为:{expect_str}")
    return header, body, url, expect_str


def is_steps(function):
    """判定是否为依赖调用"""

    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        global is_step, step_num
        cur_frame = inspect.currentframe()
        out_frame = inspect.getouterframes(cur_frame)[1][3]  # 获取调用方的名称
        # 判断依赖情况,设置is_step值
        if out_frame == "steps":
            is_step = True
        else:
            is_step = False
        resp, expect, body, header = function(*args, **kwargs)
        if out_frame == "steps":
            return resp, expect, body
        return resp, expect

    return wrapper


ignore_status_white_list = ["create_ad_026"]


def check_resp(function):
    """检查响应情况"""

    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        resp, expect, body, header = function(*args, **kwargs)
        if resp.status_code != 200 and args[1] not in ignore_status_white_list:
            error_info = f"服务响应状态码为{resp.status_code}"
            raise ValueError(error_info)
        try:
            resp.json()
        except Exception:
            error_info = f"服务响应媒体类型为{resp.headers.get('Content-Type')}"
            raise ValueError(error_info)
        return resp.json(), expect, body, header

    return wrapper


class PrintInfo(object):
    """打印请求/响应信息"""

    def __init__(self, switch=True, level=1):
        """1-依赖简单信息,2-依赖请求/响应请求信息"""
        self.switch = switch
        self.level = level

    def __call__(self, function):
        def wrapper(*args, **kwargs):
            global is_step, step_num
            resp, expect, body, header = function(*args, **kwargs)
            is_file = header.get("Content-Type")
            # 为文件上传时,请求参数不处理直接打印
            body_init = body if is_file else body
            if is_step and self.switch:
                print("依赖__信息: ", args)
                if self.level == 2:
                    print("依赖__请求数据: ", body_init)
                    print("依赖__响应: ", resp.content)
            elif not is_step:
                print("请求数据: ", body_init)
                print("响应结果是: ", json.loads(resp.text))
            return resp, expect, body, header

        return wrapper


@is_steps
@check_resp
@PrintInfo(switch=True, level=1)
def request_post(tableName, caseName):
    """post请求"""
    header, body, url, expect = request_data(tableName, caseName)
    resp = requests.post(url=url, json=eval(body), headers=header)
    return resp, expect, body, header


@is_steps
@check_resp
@PrintInfo(switch=True, level=1)
def request_get(tableName, caseName):
    """get请求"""
    header, body, url, expect = request_data(tableName, caseName)
    if body == "":
        resp = requests.get(url=url, headers=header)
    else:
        print('发起get请求')
        resp = requests.get(url=url, params=eval(body), headers=header)
    return resp, expect, body, header


def time_consume(function):
    """时间消耗计时器"""

    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        begin_time = datetime.now()
        check = function(*args, **kwargs)
        end_time = datetime.now()
        print(end_time - begin_time)
        return check

    return wrapper


@time_consume
def debugging(tableName, caseName):
    """单个用例调试"""
    excel_obj = Excel(tableName, caseName)
    data = excel_obj.get_content()
    methodType = data[col.method]
    note = ""
    if data[col.note] == "":
        note += "None"
    else:
        note += data[col.note].strip().replace(":\n", "-").replace(":\n", "-")
    print("\n用例信息:", tableName, caseName)
    print("用例描述:", note)
    if methodType.lower() == "post":
        r, e = request_post(tableName, caseName)
    else:
        r, e = request_get(tableName, caseName)
    print("检查点: ", json.dumps(e, ensure_ascii=False), type(e))
    CheckResult.CheckResult(r, e).check_result()


if __name__ == '__main__':
    debugging("iot", "v1_class_filter_6")
    debugging("iot", "v1_auth_get_old_info_clear_33")
    debugging("iot", "test_get_test")
复制代码

check_result.py

# -*- coding: utf-8 -*-
from common.SendRequests import *
from pactverify.matchers import PactJsonVerify
from pactverify.matchers import Matcher, Like, EachLike, Enum, Term, PactVerify


class CheckResult(object):
    def __init__(self, result, expect):
        self.result = result
        self.expect = expect
        self.is_result = False

    def _dict_check(self, dictExcept):
        """
        获取表格中expect字段的值,然后转化成json 契约
        :return:
        """
        expect = Matcher(json.loads(dictExcept))
        mPactVerify = PactVerify(expect, hard_mode=False)
        # 校验实际返回数据
        mPactVerify.verify(self.result)
        # 校验结果  False
        print(mPactVerify.verify_result)
        if mPactVerify.verify_result:
            self.is_result = True
        else:
            print(f"校验失败原因是:{mPactVerify.verify_info}")

    def _list_check(self, listExcept):
        """判断self.result 和listExcept 是否有交集 如果为空 则校验失败"""
        # res = list(set(self.result) & set(listExcept))
        pact_json = {'$Enum': json.loads(listExcept)}
        mPactJsonVerify = PactJsonVerify(pact_json, hard_mode=False, separator="$")
        # 校验接口返回结果
        mPactJsonVerify.verify(self.result)
        # 打印校验结果
        if mPactJsonVerify.verify_result:
            self.result = True

    def _str_check(self, strExpect):
        if strExpect != "":
            self.is_result = False

    def check_result(self):
        """分为字符串校验、字典校验、列表校验"""
        if self.expect == "":
            self._str_check(self.expect)
        expect = eval(json.dumps(self.expect, ensure_ascii=False))
        if type(json.loads(expect)) == list:
            self._list_check(expect)
        else:
            print('进入到json字典校验')
            self._dict_check(expect)
        print(f"实际结果:{json.dumps(self.result, ensure_ascii=False)}")
        assert self.is_result == True


if __name__ == '__main__':
    r, e = request_post("iot", "v1_class_filter_6")
    CheckResult(r, e).check_result()
复制代码

读取、发起请求、结果校验都服务完成之后,就可以来写用例了

import unittest

from common.SendRequests import *


class Rampage(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        pass

    @classmethod
    def tearDownClass(cls):
        pass

    def test_get_weather_1(self):
        debugging('iot', 'test_get_weather_1')

    def test_get_weather_2(self):
        debugging('iot', 'test_get_weather_2')
复制代码

一行就行。

三.报告生成了

没有报告生成的接口是不完美的

htmlrunnner太长了,我就不复制了,搞个百度网盘链接,自己去下吧

链接:pan.baidu.com/s/1ExJdaicZ… 提取码:cv53

有了htmlrunner 之后就可以来操作了

# -*-coding:utf-8 -*-
import unittest
from functools import wraps
from common.HTMLRunner import HTMLTestRunner

ReportPath = "./result.html"


def CreatReport(AA, testcase):
    """
    根据AA的True或者False 来执行哪种报告生成方式。
    区别 就是一个用到测试套件suite 一个没有
    # AA = hasattr(testcase, '__call__')
    # AA = isfunction(testcase)
    from inspect import isfunction
    """

    def Report(func):
        @wraps(func)
        def Great_report():
            fp = open(ReportPath, 'wb')
            runner = HTMLTestRunner(stream=fp, title=r'UI自动化', description=r'接口自动化测试报告')
            if AA:
                suite = unittest.makeSuite(testcase)
                runner.run(suite)
            else:
                runner.run(testcase)
            fp.close()

        func()
        return Great_report

    return Report


def CreatReports(func):
    def Report(AA, testcase):
        fp = open(ReportPath, 'wb')
        runner = HTMLTestRunner(stream=fp, title=r'UI自动化', description=r'接口自动化测试报告')
        if AA:
            suite = unittest.makeSuite(testcase)
            runner.run(suite)
        else:
            runner.run(testcase)
        fp.close()
        func(AA, testcase)

    return Report


def runCase(*args):
    suite = unittest.TestSuite()
    ids = args[1]
    while ids < args[2]:
        if ids < 10:
            basename = 'test_00' + str(ids) + '_Test'
        elif 10 <= ids < 100:
            basename = 'test_0' + str(ids) + '_Test'
        else:
            basename = 'test_' + str(ids) + '_Test'
        suite.addTest(args[0](basename))
        print('执行的用例为:{}'.format(basename))
        ids += 1
    suite.debug()
复制代码

这里通过装饰器写了报告生成方式。还有用例单独调试时,怎么调试

常规的,两种方式,第一我们只需要运行当前的这个用例py文件,它只是一个类,下面有很多用例 很多类函数而已。

第二就是 通过用例的py文件相同的开头,来一起执行,比如我的目录下有三个用例,test_1_case ,test_2_case ,test_3_case 我想要一起执行

if __name__ == '__main__':
    @CreatReport(True, Case)
    def report(): pass


    report()
复制代码

这样来调就可以执行了,通过True 和false 来判断 那种执行方法 True是单独执行、False 是批量执行。

当然批量执行,需要我们先加载testcase

def all_case():
    testcase = unittest.TestSuite()  # 加载测试套件 # 用例的目录,关键字进行匹配
    discover = unittest.defaultTestLoader.discover(filepath + "/test_case", pattern='MypageCase_Auto*.py', top_level_dir=None)
    testcase.addTest(discover)
    print(testcase)
    return testcase


@CreatReport(False, testcase=all_case())
def Run():
    pass


if __name__ == '__main__':
    Run()
复制代码

这样就可以咯

自此我们的测试报告也完成了。单独调试的意思是,根据用例名称来决定的

举个例子。我的用例名称是 test_001_Test /test_002_Test /test_002_Test

然后才有上面的这种。 调用时,需要传三个参数,第一 是当前用例的类名称 、开始序号、结束序号

例如:``` runCase(CreditMage, 120, 130)

复制代码
分类:
代码人生
标签:
收藏成功!
已添加到「」, 点击更改