接口(interface) 测试

前提

概念

接口:系统之间数据交互的通道。(本质是函数(方法))

接口测试,会绕过前端,直接对服务器进行测试

实现方式

软件:

postman:使用简单,上手难度低。功能较少。

jmeter:使用难度较大。上手难度大。功能齐全。

代码:

Python + requests + Unittest

java + HttpClient

http协议

HTTP:超文本传输协议,基于请求与响应的应用层协议

默认端口:80

https默认端口:443

特点:客户端/服务器模式、简单快速、灵活、无连接、无状态

URL:协议://ip地址:端口号/资源路径

请求:

请求行:请求方法 url 协议版本

请求头:键值对

。Content-Type:作用,指定 请求体的数据类型

请求体:数据类型受Content-Type影响

1xx 指示错误

200 成功

30x 重定向错误

403 访问被禁止( 权限不足)

404 请求资源不存在

500 服务器错误

接口风格

传统风格

restful风格

测试流程

需求分析 -- 接口文档的解析 -- 设计测试用例 -- 脚本开发 -- 缺陷跟踪 -- 生成测试报告 -- 接口自动化持续集成

接口文档解析:

接口文档:又称为API文档,是由后端开发编写,用来描述接口信息的文档。

测试维度

功能测试

。 单接口测试

。 业务场景功能测试

性能测试

。 响应时长:从发送请求到接收到服务器回发响应包所经历的时间。

。 错误率:服务器运行出错的概率

。 吞吐量:服务器单位时间内,处理请求的数量。

。 服务器资源利用率:cpu、内存、网络、磁盘等 硬件资源的占用率。

安全测试

。 攻击安全:木马,病毒...

。 业务安全:敏感数据加密存储、SQL注入...

测试用例

设计方法

单接口测试:

。 正向测试

。 反向测试

业务场景测试:

。 尽量模拟用户实际使用场景

。尽量用最少的用例,覆盖最多的接口请求

。 一般情况下,覆盖正向测试即可

编写

用例编号 用例名称 模块 优先级 前置条件 接口名称 请求方法 URL 请求头 请求数据 预期结果 实际结果

分析测试点

与功能测试时分析差不多。

Postman的使用

基本使用

1、请求方法

2、URL

3、请求头

4、请求体

5、断言

断言

pm:postman的实例

test():postman的测试方法

  • 断言响应状态码
js 复制代码
// 断言响应状态码200
pm.test("Status code is 200", function () {
    pm.response.to.have.status(200);
});

参数一:"Status code is 200" ==》 给用户看的提示信息

参数二:"function () { pm.response.to.have.status(200);}" ==》预期结果是200。

  • 断言包含的某个字符串
js 复制代码
pm.test("Body matches string", function () {
    pm.expect(pm.response.text()).to.include("string_you_want_to_search");
});

参数一:"Body matches string" ==》 给用户看的提示信息

参数二:"function () {pm.expect(pm.response.text()).to.include("预期字符串");}" ==》预期结果是应该包含"预期字符串"·

  • 断言JSON数据
js 复制代码
pm.test("Your test name", function () {
    var jsonData = pm.response.json();
    pm.expect(jsonData.value).to.eql(100);
});

参数一:"Your test name" ==》 给用户看的提示信息

参数二:"function () {var jsonData = pm.response.json();

pm.expect(jsonData.value).to.eql(100);}" ==》预期结果json中某个"value"的key值为"100"

原理:

关联

js 复制代码
// 1、获取响应数据、转为json格式,保存到变量jsonDta 中。
var jsonDta = pm.response.json()
// 2.1、设置环境变量。
pm.environment.set( "环境变量名",环境变量值)
// 2.2、使用全局变量做容器。
pm.globals.set ( "全局变量名",全局变量值)
// 3.在postman界面中提取全局、环境变量数据。
{{全局变量名}}/{{环境变量名}}
var value = pm.environment.get("var_name");	# 代码中引用

创建环境:

全局变量:在整个postman中都可以使用的变量。不需要单独创建环境。

环境变量:在特定的环境下,才能使用的变量。需要给此变量创建单独的环境。

例子一(方法步骤):

  • 1、发送获取天气的请求,获取响应结果,存入全局变量

  • 2、百度搜索接口从全局变量中,取城市名,完成搜索

例子二(令牌的存储):

参数化

  • CSV 文件

    注意:

    1、不能测试 bool类型。因为postman读取csv后,将所有非数值类型数据,自动添加""变为字符串

    2、不能存储复杂数据类型(元组,列表,字典)

    3、不能实现参数测试

  • JSON文件

    文件大
    例如:

    1、创建导入

2、引用数据文件

(1、){{keyi}} 引用相关对象的key

(2、)使用postman内置的关键字data

例如: csv文件:data.字段名;

json文件:data.键名;

只能按第一图进行操作,然后点击run.

测试报告

前提:

1、安装node.js

2、安装newman【npm install -g newman】【newman -v 测试安装成功】

3、安装newman插件【npm install -g newman-reporter-htmlextra】

1、导出用例:

2、导出环境:

3、命令生成:

newman run 用例集文件.json -e 环境文件 -r htmlextra --reporter-htmlextra-export aa.html

Postman练习

1、项目结构

2、创建环境

3、编写测试用例

4、执行测试用例(批量执行)

接口自动化

Requests库

Requests库是Python编写的,基于urllib 的HTTP库,使用方便。
安装: pip install requests

请求语法:


简介: 工程师 针对 http协议是无连接、无状态特性,设计的 一种技术。可以在浏览器端 存储用户的信息。

。 cookie 用于存储 用户临时的不敏感信息。

。 cookie 位于浏览器端。默认大小 4k(可以调整)

。 cookie 中的数据,可以随意被访问,没有安全性可言。

。 cookie 中存储的数据类型,受浏览器限制。

身份认证方式:

。 token

。 cookie +session

cookie +session认证

【就是把获取验证码的请求得到cookie,然后登录时带过去】

Session

简介: 也叫会话。通常出现在网络通信中,从客户端借助访问终端登录上服务器,直到退出登录所产生的通信数据,保存在会话中。

。 Session用于存储 用户的信息。

。 Session位于服务端。大小自接使用服务器大小。

。 Session 中的数据,不能随意被访问,安全性高。

。 Session中存储的数据类型,受服务器限制。

。 Session自动管理Cookie

session认证

获取指定响应数据:

Cookie和Seesion的区别

Cookie Seesion
存储位置 浏览器 服务器
安全性 可随意获取 加密存储
数据类型 受浏览器限制较少 直接使用服务器,支持使用所有数据类型
大小 默认4K 约为服务器大小

参数化

1、读取Jason文件

python 复制代码
def read_json_data():
    list_data = []
    # 从 .json 文件中,读取 [{},{},{}] 数据
    with open("./params_data.json", "r", encoding="utf-8") as f:
        data = json.load(f)
        for item in data:
            tmp = tuple(item.values())  # 将字典里的values转成元组
            list_data.append(tmp)		# 转成[(),(),(),()]格式
    return list_data

2、参数化

步骤:

  • 导包:from parameterized import parameterized
  • 在通用测试方法上添加一行
  • 并传参元组列表数据(调用自己封装的读取json文件的函数):@parameterized.expand(read_json_data())
  • 修改通用测试方法形参,与json文件中的key保持一致
  • 在通用测试方法内,使用形参
python 复制代码
@parameterized.expand(read_json_data())
def test_add(self, cu1, cu2, cu3):
    res = add(cu1, cu2)
    self.assertEqual(cu3, res)

接口自动化框架

类似于分层架构,分包管理

目录结构:

首先在接口对象成,编写用例代码,然后在接口用例成执行代码,其中断言需要调用工具包的方法,数据需要调用数据文件

接口对象层

文件夹名:api

文件名:hr_login_api

python 复制代码
import requests
class HrLoginApi(object):
    # 登录
    @classmethod
    def login(cls, json_data):
        url = "http://xxxxxxxx/xxxx/login"
        header = {"Content-Type": "application/json"}
        resp = requests.post(url=url, headers=header, json=json_data)
        return resp

接口用例层

文件夹名:scripts

文件名:test_hr_login

python 复制代码
import unittest
from parameterized import parameterized
from api.hr_login_api import HrLoginApi
from common.assert_util import assert_util
from common.read_json_util import read_json_data

class TestHrLogin(unittest.TestCase):
    @parameterized.expand(read_json_data())
    def test_login_sucess(self, desc, req_data, status_code, success, code, message):
        # 调用自己的接口
        resp = HrLoginApi.login(req_data)
        # 断言
        assert_util(self, resp, status_code, success, code, message)

工具类

文件夹名:common

文件名:assert_util

python 复制代码
# 定义通用断言方法
def assert_util(self, resp, status_code, success, code, message):
    self.assertEqual(status_code, resp.status_code)
    self.assertEqual(success, resp.json().get("success"))
    self.assertEqual(code, resp.json().get("code"))
    self.assertIn(message, resp.json().get("message"))

文件夹名:common

文件名:read_json_util.py

python 复制代码
# 定义函数读取Json文件
import json

def read_json_data():
    list_data = []
    with open("../data/params_data.json", "r", encoding="utf-8") as f:
        json_data = json.load(f)
        for item in json_data:
            tem = tuple(item.values())
            list_data.append(tem)
    return list_data

数据文件

文件夹名:data

文件名:params_data.json

json 复制代码
[
  {
    "desc": "登录成功",
    "req_data":{"mobile": "13800000002", "password": "123456"},
    "status_code": 200,
    "success": true,
    "code": 1000,
    "message": "操作成功"

  },
  {
    "desc": "手机号为空",
    "req_data":{"mobile": null, "password": "123456"},
    "status_code": 200,
    "success": false,
    "code": 2001,
    "message": "用户名或密码错误"

  }
]

测试报告文件

日志收集

log:记录当前系统运行是的信息。

作用:查看系统是否正常,定位bug

日志级别:日志级别与严重性相反 ,只会记录比自己低级别和相同级别的日志

代码如下:

python 复制代码
"""
步骤:
# 0. 导包
# 1. 创建日志器对象
# 2. 设置日志打印级别
    # logging.DEBUG 调试级别
    # logging.INFO 信息级别
    # logging.WARNING 警告级别
    # logging.ERROR 错误级别
    # logging.CRITICAL 严重错误级别
# 3. 创建处理器对象
    # 创建 输出到控制台 处理器对象
    # 创建 输出到日志文件 处理器对象
# 4. 创建日志信息格式
# 5. 将日志信息格式设置给处理器
    # 设置给 控制台处理器
    # 设置给 日志文件处理器
# 6. 给日志器添加处理器
    # 给日志对象 添加 控制台处理器
    # 给日志对象 添加 日志文件处理器
# 7. 打印日志
"""
import logging.handlers
import logging
import time

# 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 创建 输出到日志文件 处理器对象
fh = logging.handlers.TimedRotatingFileHandler('a.log', when='midnight', interval=1,
                                               backupCount=3, encoding='utf-8')
# when 字符串,指定日志切分间隔时间的单位。midnight:凌晨:12点。
# interval 是间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
# backupCount 是保留日志文件的个数

# 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)

# 7. 打印日志
while True:
    # logging.debug('我是一个调试级别的日志')
    logging.info('我是一个信息级别的日志')
    # logging.warning('我是一个警告级别的日志')
    # logging.error('我是一个错误级别的日志')
    # logging.critical('我是一个严重错误级别的日志')
    time.sleep(1)

封装成工具类:

python 复制代码
import logging.handlers
import logging
import time


def init_log_config(filename, when='midnight', interval=1, backup_count=7):
    """
    功能:初始化日志配置函数
    :param filename: 日志文件名
    :param when: 设定日志切分的间隔时间单位 'mindnight'
    :param interval: 间隔时间单位的个数,指等待多少个 when 后继续进行日志记录
    :param backup_count: 保留日志文件的个数
    :return:
    """
    # 1. 创建日志器对象
    logger = logging.getLogger()

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

    # 3. 创建处理器对象
    # 控制台对象
    st = logging.StreamHandler()
    # 日志文件对象
    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. 给处理器设置日志信息格式
    st.setFormatter(formatter)
    fh.setFormatter(formatter)

    # 6. 给日志器添加处理器
    logger.addHandler(st)
    logger.addHandler(fh)


if __name__ == '__main__':
    # 初始化日志
    init_log_config('a.log')
    # 打印输出日志信息
    logging.debug('我是一个调试级别的日志')

全量字段校验

1、定义校验规则

2、比对

unittest框架

主要用于管理测试用例。

注意遵守标识符命名规范:

  • 只能使用 字母、数字、下划线。数字不能开头。避免使用 关键字、已知函数名
  • 类:(首字母必须大写。建议以 Test 开头)
  • 方法:(必须 test 开头,建议 编号)

基本用法测试

  • 1、创建测试用例
python 复制代码
import requests
# 导包
import unittest


class Test01(unittest.TestCase):    # 使用TestCase,创建测试用例
    # def 定义的test_ 是测试用例,只有执行 if __name__ == '___mian___'
    # 的时候会执行测试用例,其他普通函数则不执行,通过 self 来调用执行。
    def test_01(self):  # 测试方法必须以test_开头
        url = 'https://apiuat.jaspervault.io/api/options-dates/'
        params = {'network_id': 1, 'collateral_token_id': 1, 'collateral_token_id': 2, 'collateral_token_id': 'hedge'}
        req = requests.get(url, params)
        print(req.json())
  • 2、执行测试用例
python 复制代码
import unittest
from htmltestreport import HTMLTestReport
from test.test_aa03 import Test01

# TestSuite,测试套件
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(Test01))  # 将测试类的所有方法进行添加

runner = unittest.TextTestRunner()  # 实例化运行对象

runner = HTMLTestReport("测试报告1.html")   # 自动化生成测试报告

runner.run(suite)   # 运行对象
  • 批量增加测试方法到测试套件中
    写法:
  1. suite = unittest.TestLoader().discover("指定搜索的目录文件","指定字母开头模块文件")
  2. suite = unittest.defaultTestLoader.discover("指定搜索的目录文件","指定字母开头模块文件") 【推荐】
    注意:
    如果使用写法1,TestLoader()必须有括号。

Fixture(测试夹具)

是一种代码结构,在某些特定情况下,会自动执行。

方法级别:每个方法执行前后都要执行

def setUp(),每个测试方法执行之前都会执行 (初始化)

def tearDown(),每个测试方法执行之后都会执行 (释放)

类级别:在每个测试类中所有方法执行前后 都会自动调用的结构(在整个类中 执行之前执行之后各一次)

类方法必须使用 @classmethod修饰

def setUpClass() ,类中所有方法之前

def tearDownClass(),类中所有方法之后

断言

  • 断言的结果:

    1、True,用例通过

    2、False,代码抛出异常,用例不通过

    3、在unittest中使用断言,需要通过 self.断言方法

  • 常用的断言:

txt 复制代码
self.assertEqual(ex1, ex2) 			# 判断ex1 是否和ex2 相等
self.assertIn(ex1, ex2) 			#  ex2是否包含 ex1   注意:所谓的包含不能跳字符
self.assertTrue(ex)					#  判断ex是否为True

重点讲前两个assertEqual 和 assertIn
方法:
assertEqual:self.assertEqual(预期结果,实际结果)          判断的是预期是否相等实际
assertIn:self.assertIn(预期结果,实际结果) 				判断的是预期是否包含实际中
assertIn('admin', 'admin') 								# 包含
assertIn('admin', 'adminnnnnnnn') 						# 包含
assertIn('admin', 'aaaaaadmin') 						# 包含
assertIn('admin', 'aaaaaadminnnnnnn') 					# 包含
assertIn('admin', 'addddddmin') 						# 不是包含

标准语法

python 复制代码
import unittest


class demo(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:    # 执行该类前进行
        print("setUpClass")

    @classmethod
    def tearDownClass(cls) -> None:
        print("tearDownClass")      # 执行该类后进行

    def setUp(self) -> None:    # 执行每个用例前进行
        print("setup")

    def tearDown(self) -> None:     # 执行每个用例后进行
        print("down")

    def test_case01(self):
        print("test_case01")
        self.assertEqual(1, 1, "判断相等")

    @unittest.skip  # 跳过该用例
    def test_case02(self):
        print("test_case02")
        self.assertEqual(2, 1, "判断相等")

PyMySQL操作数据库

使用场景:校验测试数据(字段会改,返回数据没有),构造测试数据(数据只能用一次,无法保证数据已存在)

安装

pip install pymysql

操作步骤

1.导包import pymysql

2.创建连接。conn=pymysql.connect()

3.获取游标。cursor=conn.cursor()

4.执行 SQL。cursor.execute("sql语句")

5.关闭游标,关闭连接

1.导包

import pymysql

2、创建连接

conn = pymysql.connect(host=" ",port=0,user=" ",password=" ",database=" ",charset=" ")

host:数据库所在IP地址

port:端口

user:用户名

password:密码

database:连接那个数据库的名字

charset:字符集。utf8

conn :数据库对象

3、获取游标

cursor=conn.cursor()

4、执行SQL语句

。 查询语句

提取数据:cursor.fetch*()

。 增删改语句

成功:提交事务 conn.commit()

失败:回滚事务 conn.rollback()

【查询】

cursor.fetch*() #默认在0号位,提取数据在所在位置的下一行,每提一条自动向下移动。

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

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

。。 fetchall():提取所有结果集。

。。 属性rownumber:可以设置游标位置。 cursor.rownumber=0

【添加】【修改】

。。 成功:提交事务 conn.commit()

。。 失败:回滚事务 conn.rollback()

5、关闭

关闭游标:cursor.close()

关闭连接:conn.close()

封装

pymysql封装

python 复制代码
import pymysql


class DBUtil(object):
    conn = None
    cursor = None

    @classmethod
    def __get_conn(cls):
        if cls.conn is None:
            cls.conn = pymysql.connect(host="127.0.0.1", port=3306, user="root",
                                       password="root", database="test01", charset="utf8")
        return cls.conn

    @classmethod
    def __close_conn(cls):
        if cls.conn is not None:
            cls.conn.close()
            cls.conn = None
        pass

    @classmethod
    def select_one(cls, sql):
        try:
            # 获取连接
            cls.conn = cls.__get_conn()

            # 执行sql语句
            cursor = cls.conn.cursor()
            cursor.execute(sql)
            res = cursor.fetchone()
        except Exception as err:
            print("查询sql错误", str(err))
        finally:
            # 关闭连接
            cls.__close_conn()

        return res

    @classmethod
    def uid_db(cls, sql):
        cursor = None
        try:
            # 连接
            cls.conn = cls.__get_conn()
            cursor = cls.conn.cursor()
            # sql
            cursor.execute(sql)
            print("影响行数:", cls.conn.affected_rows())
            # 提交
            cls.conn.commit()
        except Exception as err:
            print("err", err)
            # 回滚
            cls.conn.rollback()
        finally:
            # 关闭
            cursor.close()
            cls.__close_conn()

测试

简单来说就是将重复的代码进行封装

  • 接口对象层
    封装接口的
  • 测试脚本层
    封装断言的
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms5 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下5 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。5 天前
2026.2.25监控学习
学习
im_AMBER5 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J5 天前
从“Hello World“ 开始 C++
c语言·c++·学习