做测试,不会接口测试怎么能行?

接口测试的测试点

功能测试:单接口功能、业务场景功能

性能测试:响应时长、错误率、吞吐量、服务器资源使用率

安全测试:敏感数据是否加密、SQL注入、其他

本篇文章以接口功能测试为主。

接口用例设计方法:

单接口测试用例设计:

正向用例设计:

(1)必选参数。所有必选参数给正确数据

(2)组合参数:所有必选+任意可选,给正确数据

(3)全部参数:所有必选+所有可选,给正确参数

反向用例设计:

(1)功能异常:数据格式正确,不能履行接口功能

(2)数据异常:数据格式不正确(空格、特殊字符、字母、长度----等价类,边界值)

(3)参数异常:(多参、少参、无参、错误参数)

业务场景接口测试

(1)尽量模拟用户实际使用场景

(2)尽量用最少的用例,覆盖最多的接口请求

(3)一般情况下,覆盖正向的测试即可

案例1:登录接口用例设计(以下用例只是部分)

image.png

案例2:添加用户用例设计(以下用例只是部分)

image.png

接口测试工具-postman

关于postman的详细操作见:

接口测试工具之postman juejin.cn/post/695424…

使用requests库实现接口测试

Requests库 是 Python编写的,基于urllib 的 HTTP库,使用方便。

安装:pip install requests

或者指定国内镜像源安装:pip install requests -i https://pypi.douban.com/simple/

设置http请求语法

resp = requests.请求方法(url='URL地址', params={k:v}, headers={k:v},data={k:v}, json={k:v}, cookies='cookie数据'(如:令牌))

入门案例:访问百度接口

image.png

我们在练习过程中,可以使用开源商城tpshop来进行练习,具体安装在百度上搜索即可

案例:开源商城tpshop的登录接口

import requests

# 发送 post 请求,指定url、请求头、请求体,并获取响应结果
resp = requests.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                     headers={"Content-Type": "application/x-www-form-urlencoded"},
                     data={"username": "13012345678", "password": "1234567", "verify_code": "8888"})

# 打印响应结果 - 文本
print(resp.text)
# 打印响应结果 - json
print(resp.json())

案例:商城调用登录接口,增加cookie参数(tpshop项目:cookie+Session认证。

image.png

import requests

resp_v = requests.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")

my_cookie = resp_v.cookies

resp = requests.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                     headers={"Content-Type": "application/x-www-form-urlencoded"},
                     data={"username": "18800000000", "password": "1234567", "verify_code": "8888"},
                     cookies=my_cookie)

print(resp.json())

image.png

以下方式是使用session认证,不需要获取cookie--掌握

import requests

# 1. 创建一个 Session 实例。
session = requests.session()
# 2. 使用 Session 实例,调 get方法,发送获取验证码请求。
session.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")
# 3. 使用 同一个 Session 实例,调用 post方法,发送登录请求。
resp = session.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                    headers={"Content-Type": "application/x-www-form-urlencoded"},
                    data={"username": "18892089999", "password": "123456", "verify_code": "8888"})

print(resp.json())

面试题 Cookie 和 Session 区别

  1. 数据存储位置:

cookie存储在浏览器;session存储在服务器。

  1. 安全性:

cookie中的数据可以随意获取,没有安全性可言。Session的数据多为加密存储,安全较高!

  1. 数据类型:

cookie支持的数据类型受浏览器限制,较少;Session直接使用服务器存储,支持所有数据类型

  1. 大小:

cookie大小默认 4k; Session 大小约为服务器存储空间大小

Unittest框架集成Requests库

Unittest的相关详细知识见下面文档:

Python框架之UnitTest: juejin.cn/post/712533…

案例:登录接口,单个测试方法

import unittest
import requests


class TestLogin(unittest.TestCase):
    def test01_login(self):
        resp = requests.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                             headers={"Content-Type": "application/x-www-form-urlencoded"},
                             data={"username": "18892081111", "password": "123456", "verify_code": "8888"})
        print(resp.json())

        # 断言状态码
        self.assertEqual(200, resp.status_code)

        # 断言 success的值为True
        self.assertEqual(True, resp.json().get("success"))

PyMySQL操作数据库

应用场景:

校验测试数据:接口发送请求之后,明确会对数据库中的某个字段进行修改,但是响应结果中不体现

构造测试数据:比如测试数据使用一次就失败(用手机号添加员工);测试前,无法保证测试数据一定存在(比如:列表查询)

安装PyMySQLpip install PyMySQL

或者pip install PyMySQL -i https://pypi.douban.com/simple/

操作数据库的基本流程:

1、导包;import pymysql

2、创建数据库连接(connection);conn = pymysql.connect()

3、获取游标对象(cursor);cursor = conn.cursor()

4、执行操作;cursor.execute('sql语句')

(4.1)查询语句:不会对数据库产生影响,有结果集返回,使用fetch提取返回数据;

(4.2)增删改语句:会对数据库产生影响。成功:提交事务:conn.commit();失败:回滚事务:conn.rollback()

5、关闭游标对象(cursor);cursor.close()

6、关闭数据库连接(connection);conn.close()

案例:

# 1、导包;`
import pymysql
# 2、创建数据库连接(connection);`conn = pymysql.connect()
conn = pymysql.connect(host="localhost",port=3306,user="root",password="root",database="mysql",charset="utf8")
# 3、获取游标对象(cursor);`cursor = conn.cursor()
cursor = conn.cursor()
# 4、执行操作;`cursor.execute('sql语句')`
cursor.execute('select version()')
# 提取一行结果
res = cursor.fetchone()
print(res) # 返回元组
print(res[0]) #取返回的第一个值
# 5、关闭游标对象(cursor);`cursor.close()`
cursor.close()
# 6、关闭数据库连接(connection);`conn.close()`
conn.close()

常用方法说明:

fetchone():从结果集中提取一行

fetchmany(size):从结果集中提取size行

fetchall():提取所有结果集

属性rownumber:可以设置游标位置(cursor.rownumber = 0(设置游标归零))

数据库工具类封装:

(1)获取、关闭连接

import pymysql
# 封装数据库工具类
class DBUtil(object):
    # 添加类属性
    conn = None

    @classmethod
    def __get_conn(cls):
        # 判断 conn 是否为空, 如果是,再创建
        if cls.conn is None:
            cls.conn = pymysql.connect(host="localhost",port=3306,user="root",password="root",database="mysql",charset="utf8")
        # 返回非空连接
        return cls.conn

    @classmethod
    def __close_conn(cls):
        # 判断conn 不为空,需要关闭。
        if cls.conn is not None:
            cls.conn.close()
            cls.conn = None

(2)查询一条记录

# 常用方法:查询一条结果
    @classmethod
    def select_one(cls, sql):
        cursor = None
        res = None
        try:
            # 获取连接
            cls.conn = cls.__get_conn()
            # 获取游标
            cursor = cls.conn.cursor()
            # 执行 查询语句
            cursor.execute(sql)
            # 提取一条结果
            res = cursor.fetchone()
        except Exception as err:
            print("查询sql错误:", str(err))
        finally:
            # 关闭游标
            cursor.close()
            # 关闭连接
            cls.__close_conn()
            # 将查询sql执行的 结果,返回
            return res

if __name__ == '__main__':
    res = DBUtil.select_one("select * from person;")
    print("查询结果为:", res)

(3)增删改数据时方法封装

# 常用方法:增删改数据
    @classmethod
    def uid_db(cls, sql):
        cursor = None
        try:
            # 获取连接
            cls.conn = cls.__get_conn()
            # 获取游标
            cursor = cls.conn.cursor()
            # 执行 uid 语句
            cursor.execute(sql)
            print("影响的行数:", cls.conn.affected_rows())
            # 提交事务
            cls.conn.commit()
        except Exception as err:
            # 回滚事务
            cls.conn.rollback()
            print("增删改 SQL 执行失败:", str(err))
        finally:
            # 关闭游标
            cursor.close()
            # 关闭连接
            cls.__close_conn()

if __name__ == '__main__':
    DBUtil.uid_db("update person set age = 30 where id = 2;")

接口对象封装

核心思想:代码分层思想,分为接口对象层和测试脚本层

(1)接口对象层:对接口进行封装,封装好之后,给测试用例层调用,面向对象类封装实现。

(2)测试用例层:调用接口对象层封装的方法,获取响应结果,断言进行接口测试,借助unittest框架实现。

封装思想:

(1)将动态变化的数据设计到方法的参数;

(2)将固定不变的,直接写成方法的实现;

(3)将响应结果通过返回值传出;

我们以TPshop开源商城为例,在没有封装之前的代码为:

import unittest
import requests


class TestTpshopLogin(unittest.TestCase):
    # 测试登录成功
    def test01_login_ok(self):
        # 创建 session 实例
        session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
        session.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")
        # 使⽤实例,调⽤post 发送登录请求
        resp = session.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                            headers={"Content-Type": "application/x-www-form-urlencoded"},
                            data={"username": "18892089999", "password": "123456", "verify_code": "8888"})
        print("响应结果 =", resp.json())
        # 断⾔:
        self.assertEqual(200, resp.status_code)
        self.assertEqual(1, resp.json().get("status"))
        self.assertEqual("登陆成功", resp.json().get("msg"))

    # 测试⼿机号不存在
    def test02_tel_not_exists(self):
        # 创建 session 实例
        session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
        session.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")
        # 使⽤实例,调⽤post 发送登录请求
        resp = session.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                            headers={"Content-Type": "application/x-www-form-urlencoded"},
                            data={"username": "18892099999", "password": "123456", "verify_code": "8888"})
        print("响应结果 =", resp.json())
        # 断⾔:
        self.assertEqual(200, resp.status_code)
        self.assertEqual(-1, resp.json().get("status"))
        self.assertEqual("账号不存在!", resp.json().get("msg"))

    # 测试密码错误
    def test03_pwd_err(self):
        # 创建 session 实例
        session = requests.Session()
        # 使⽤实例,调⽤get 发送获取验证码请求
        session.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")
        # 使⽤实例,调⽤post 发送登录请求
        resp = session.post(url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
                            headers={"Content-Type": "application/x-www-form-urlencoded"},
                            data={"username": "18892089999", "password": "999999", "verify_code": "8888"})
        print("响应结果 =", resp.json())
        # 断⾔:
        self.assertEqual(200, resp.status_code)
        self.assertEqual(-2, resp.json().get("status"))
        self.assertEqual("密码错误!", resp.json().get("msg"))

对以上代码进行封装:

封装接口对象层:

class TpshopLoginApi(object):

    # 发送验证码请求
    @classmethod
    def get_verify(cls, session):
        session.get(url="http://127.0.0.1/index.php?m=Home&c=User&a=verify&r=0.47350402574416184")

    # 发送登录请求
    @classmethod
    def login(cls, session, login_data):
        resp = session.post(
            url="http://127.0.0.1/index.php?m=Home&c=User&a=do_login&t=0.04841131938292054",
            data=login_data)
        return resp

断言方法封装:

def common_assert(self, resp, status_code, status, msg):
    # 断⾔:
    self.assertEqual(status_code, resp.status_code)
    self.assertEqual(status, resp.json().get("status"))
    self.assertEqual(msg, resp.json().get("msg"))

测试用例层封装结果:

import unittest
import requests
from TPshop_login_api import TpshopLoginApi
from tp_shop_assert import common_assert


class TestShopLogin(unittest.TestCase):
    # 添加类属性
    session = None

    @classmethod
    def setUpClass(cls) -> None:
        cls.session = requests.Session()

    def setUp(self) -> None:
        TpshopLoginApi.get_verify(self.session)

    # 测试登录成功
    def test01_login_ok(self):
        login_data = {"username": "18892089999", "password": "123456", "verify_code": "8888"}
        resp = TpshopLoginApi.login(self.session, login_data)
        print("登录成功的结果:", resp.json())
        common_assert(self, resp, 200, 1, "登陆成功")

    # 测试⼿机号不存在
    def test02_tel_not_exists(self):
        login_data = {"username": "18892099999", "password": "123456", "verify_code": "8888"}
        resp = TpshopLoginApi.login(self.session, login_data)
        print("登录成功的结果:", resp.json())
        # 断⾔:
        common_assert(self, resp, 200, -1, "账号不存在!")

    # 测试密码错误
    def test03_pwd_err(self):
        login_data = {"username": "18892089999", "password": "999999", "verify_code": "8888"}
        resp = TpshopLoginApi.login(self.session, login_data)
        print("登录成功的结果:", resp.json())
        # 断⾔:
        common_assert(self, resp, 200, -2, "密码错误!")

以上代码参数化:

# 参数化的数据,实际项目中需要单独保存
json_data = [
    {
        "req_body": {"username": "18892080505", "password": "123456", "verify_code": "8888"},
        "status_code": 200,
        "status": 1,
        "msg": "登陆成功"
    },
    {
        "req_body": {"username": "18892099999", "password": "123456", "verify_code": "8888"},
        "status_code": 200,
        "status": -1,
        "msg": "账号不存在!"
    },
    {
        "req_body": {"username": "18892080505", "password": "999999", "verify_code": "8888"},
        "status_code": 200,
        "status": -2,
        "msg": "密码错误!"
    }
]


# 封装函数,将 数据 转换为 元组列表。
def read_json_data():
    # 将[{},{},{},{}]格式转化成[(),(),(),()]
    list_data = []
    for item in json_data:
        tmp = tuple(item.values())
        list_data.append(tmp)

    print(list_data)
    return list_data

在通用方法中实现参数化:

class TestShopLogin(unittest.TestCase):
    # 添加类属性
    session = None

    @classmethod
    def setUpClass(cls) -> None:
        cls.session = requests.Session()

    def setUp(self) -> None:
        TpshopLoginApi.get_verify(self.session)

    # 测试登录
    @parameterized.expand(read_json_data())
    def test_tpshop_login(self, req_body, status_code, status, msg):
        resp = TpshopLoginApi.login(self.session, req_body)
        print("登录成功的结果:", resp.json())
        common_assert(self, resp, status_code, status, msg)

接口自动化测试框架思想

目录结构

5个目录、2个文件:

api/: 存储接口对象层(自己封装的 接口)

scripts/: 存储测试脚本层 (unittest框架实现的 测试类、测试方法)

data/: 存储 .json 数据文件

report/: 存储 生成的 html 测试报告

common/: 存储 通用的 工具方法

confifig.py: 存储项目的配置信息(全局变量)

run_suite.py: 组装测试用例、生成测试报告的 代码

日志

日志的级别

logging.DEBUG:调试级别【高】

logging.INFO:信息级别【次高】

logging.WARNING:警告级别【中】

logging.ERROR:错误级别【低】

logging.CRITICAL:严重错误级别【极低】

特性:日志级别设定后,只有比该级别低的日志会写入日志。

日志代码,无需手写实现。会修改、调用即可!

import logging.handlers
import logging
import time


def init_log_config(filename, when='midnight', interval=1, backup_count=3):
    # 1. 创建日志器对象
    logger = logging.getLogger()

    # 2. 设置日志打印级别
    logger.setLevel(logging.DEBUG)
    # logging.DEBUG 调试级别
    # logging.INFO 信息级别
    # logging.WARNING 警告级别
    # logging.ERROR 错误级别
    # logging.CRITICAL 严重错误级别

    # 3.1 创建 输出到控制台 处理器对象
    st = logging.StreamHandler()
    # 3.2 创建 输出到日志文件 处理器对象
    # when 字符串,指定日志切分间隔时间的单位。midnight:凌晨:12点。
    # interval 是间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
    # backupCount 是保留日志文件的个数
    fh = logging.handlers.TimedRotatingFileHandler(filename, when=when, interval=interval, backupCount=backup_count, encoding='utf-8')

    # 4. 创建日志信息格式
    fmt = "%(asctime)s %(levelname)s [%(filename)s(%(funcName)s:%(lineno)d)] - %(message)s"
    formatter = logging.Formatter(fmt)

    # 5.1 日志信息格式 设置给 控制台处理器
    st.setFormatter(formatter)
    # 5.2 日志信息格式 设置给 日志文件处理器
    fh.setFormatter(formatter)

    # 6.1 给日志器对象 添加 控制台处理器
    logger.addHandler(st)
    # 6.2 给日志器对象 添加 日志文件处理器
    logger.addHandler(fh)


if __name__ == '__main__':
    # 初始化日志
    init_log_config('tp_shop_log.log', interval=3, backup_count=5)
    # 打印输出 日志信息
    logging.warning("这是警告日志的信息")
    logging.debug("这是debug日志信息")
    a = 9999
    logging.error(f"这是error级别的信息a = {a}")

全量字段的校验-对断言的补充

流程:

(1)定义json语法校验格式

(2)⽐对接口实际响应数据是否符合json校验格式

全量字段校验需要安装jsonschema

pip install jsonschema -i https://pypi.douban.com/simple/

在线工具校验:www.jsonschemavalidator.net

image.png

python代码入门:

# 1. 导包
import jsonschema

# 2. 创建校验规则
schema = {
  "type": "object",
  "properties": {
    "success": {
      "type": "boolean"
    },
    "code": {
      "type": "integer"
    },
    "message": {
      "type": "string"
    }
  },
  "required": ["success","code","message"]
}

# 准备待校验的数据
data = {
  "success": True,
  "code": 200,
  "message": "操作成功"
}

# 3. 调用 validate 方法,实现校验

result = jsonschema.validate(instance = data,schema=schema)
print("result = ", result)
# None: 代表校验通过
# ValidationError:数据与校验规则不符
# SchemaError: 校验规则语法有误

JSON Schema语法中常用的关键字(来源某视频课程):

image.png

(1)type关键字:约束数据类型

integer —— 整数
string —— 字符串
object —— 对象
array —— 数组 --> python:list 列表
number —— 整数/⼩数
null —— 空值 --> python:None
boolean —— 布尔值

语法:
{
    "type": "数据类型"
}

(2)properties关键字:是 type关键字的辅助。用于 type 的值为 object 的场景。指定对象中 每个字段的校验规则。 可以嵌套使用。

语法:
{
    "type": "object",
    "properties":{
    "字段名1":{规则},
    "字段名2":{规则},
......
}
}

(3)required关键字:校验对象中必须存在的字段。字段名必须是字符串,且唯⼀

语法:
{
"required": ["字段名1", "字段名2", ...]
}

(4)const关键字:校验字段值是⼀个固定值。

语法:
{
"字段名":{"const": 具体值}
}

(5)pattern关键字:指定正则表达式,对字符串进行模糊匹配

语法:

{
"字段名":{"pattern": "正则表达式"}
}

补充中......

行动吧,在路上总比一直观望的要好,未来的你肯定会感 谢现在拼搏的自己!如果想学习提升找不到资料,没人答疑解惑时,请及时加入扣群: 320231853,里面有各种软件测试+开发资料和技术可以一起交流学习哦。

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值