接口(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()

测试

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

  • 接口对象层
    封装接口的
  • 测试脚本层
    封装断言的
相关推荐
Y1anoohh2 分钟前
驱动学习专栏--写在前面
学习
小学生搞程序4 分钟前
学习Python的优势体现在哪些方面?
开发语言·python·学习
李匠202415 分钟前
C++学习之工厂模式-套接字通信
c++·学习
A林玖27 分钟前
【学习笔记】服务器上使用 nbconvert 将 Jupyter Notebook 转换为 PDF
服务器·笔记·学习
ling__wx41 分钟前
go学习记录(第一天)
学习·go
前端熊猫1 小时前
React Native (RN)的学习上手教程
学习·react native·react.js
M_chen_M1 小时前
es6学习02-let命令和const命令
前端·学习·es6
M_chen_M1 小时前
JS6(ES6)学习01-babel转码器
前端·学习·es6
天才测试猿2 小时前
Postman接口测试详解
自动化测试·软件测试·python·测试工具·测试用例·接口测试·postman
李匠20242 小时前
C++学习之金融类安全传输平台项目git
c++·学习