1. 项目整体思路:Django 接口与 Jmeter 测试该怎么搭
前后端分离的项目里,后端把接口写好只是第一步,真正让接口稳定可用,还得靠持续的测试。我这些年做接口开发,最常用的组合就是 Python Django 写接口、Jmeter 做测试。Django 本身自带 ORM、Admin、认证体系,再配上 Django REST Framework(简称 DRF),写一套规范的 JSON 接口非常顺手。Jmeter 则是开源工具里最老牌、最普及的那一类,不管团队有没有商业测试平台,装一个 Jmeter 就能把接口跑起来,功能测试、并发压测、参数化关联全都能干。这篇文章就从零开始讲:怎么用 Django 把接口写出来,再用 Jmeter 把这些接口完整测一遍。
先说适用范围。如果你接触过 Django 或者其它 Web 框架,但还没系统地测过自己的接口;或者你已经会用 Postman 手动调接口,但对批量测试和并发场景没把握,那这篇文章正好合适。我不打算给你堆概念,而是把整个流程拆成可以照着做的步骤:先搭接口,再搭测试计划,中间穿插那些自己踩过的坑和总结出来的技巧。
1.1 为什么是 Django + DRF + Jmeter
选型这件事,很多人喜欢追新框架,但在实际团队里,稳定和熟练才是第一位的。Django 的生态非常完整,用户认证、数据库迁移、后台管理这些日常工作都有现成的解决方案,不需要重复造轮子。DRF 在 Django 之上补足了序列化、视图集、认证策略这些接口开发能力,写接口时只需要关注业务逻辑本身。
Jmeter 则解决了"怎么证明接口能用"的问题。它不像 Postman 那样只适合单接口调试,而是可以一次性模拟几十个、几百个用户同时访问接口,观察响应时间、错误率、吞吐量。Jmeter 还支持从 CSV 文件读取测试数据,把同一个接口用不同的参数跑很多遍,排查特殊值、边界值的问题。这些能力在接口交付之前几乎是必需的。
1.2 接口需求梳理与测试目标
在动手写代码之前,最好先把要测试的接口清单列出来。我这次以一个模拟商城项目为例,设计三类核心接口:用户登录接口、商品列表接口、订单创建接口。登录接口负责验证身份并返回令牌;商品列表接口是典型的分页查询场景;订单创建接口则依赖前一步拿到的登录状态。这三类接口覆盖了"无状态接口"和"需要认证的接口",正好把 Jmeter 里的常见操作都串起来。
测试目标也要提前定清楚。功能层面,要确认每个接口在不同参数下的返回结果符合预期;性能层面,要确认在 50 个并发用户的情况下,接口平均响应时间在可接受范围内,并且没有请求丢失。有了明确目标,后面配置 Jmeter 时就知道该加哪些断言、跑多大的并发数,而不是漫无目的地乱测。
2. Django 端接口开发全流程
2.1 环境准备与依赖选型
开发接口时,我习惯为每个项目单独创建虚拟环境,避免不同项目之间的包版本互相干扰。创建虚拟环境的命令很简单,在项目目录下执行:
python -m venv venv source venv/bin/activate激活虚拟环境之后,再安装依赖。核心依赖包括 Django、DRF 以及用于令牌认证的 djangorestframework-simplejwt。注意版本之间要匹配,我常用的组合是 Django 4.2 配 DRF 3.14,再配上 simplejwt 5.2,这套组合在实际项目中跑得很稳。安装命令如下:
pip install django djangorestframework djangorestframework-simplejwt装完后,用 django-admin 创建项目和应用:
django-admin startproject shop_project cd shop_project python manage.py startapp shop创建完成后,需要把上述依赖注册到项目的 settings.py 里。INSTALLED_APPS 中要加入 rest_framework、rest_framework_simplejwt 和 shop 应用。同时,为了让接口在测试时不被跨域问题拦住,还要安装 django-cors-headers 并在中间件里加上 CorsMiddleware。这个包在前后端分离开发中几乎必装,否则浏览器调试时会看到跨域报错。
2.2 模型与序列化器设计
写接口的本质是操作数据,所以先要把数据模型定义好。以商品和订单为例,我在 shop/models.py 里写下如下模型:
from django.db import models class Product(models.Model): name = models.CharField(max_length=100) price = models.DecimalField(max_digits=10, decimal_places=2) stock = models.IntegerField(default=0) created_at = models.DateTimeField(auto_now_add=True) class Order(models.Model): order_no = models.CharField(max_length=32, unique=True) product = models.ForeignKey(Product, on_delete=models.CASCADE) quantity = models.IntegerField() total_amount = models.DecimalField(max_digits=10, decimal_places=2) created_at = models.DateTimeField(auto_now_add=True)定义好模型之后,执行python manage.py makemigrations和python manage.py migrate生成数据库表。这个步骤容易忘,实际开发中我经常写完模型忘记迁移,结果接口一调就报表不存在的错误。
序列化器是 DRF 的核心,它的作用是把 Python 对象转成 JSON,同时负责校验前端传来的参数。我在 shop/serializers.py 中加入 ProductSerializer 和 OrderSerializer。要注意的细节是,创建订单时需要对库存做校验,这部分逻辑放在序列化器的 validate 方法里,比如库存不足就直接抛 ValidationError。这样的设计能把校验逻辑收拢到一处,后续用 Jmeter 测试非法下单场景时,返回的错误信息是统一格式,方便断言。
2.3 视图与路由实现
DRF 提供了多种视图写法,简单接口用 APIView,涉及列表和详情操作可以用 ModelViewSet。我这里采用 APIView 配合装饰器的方式,代码更好理解,也方便在接口里加自定义逻辑。
用户在 shop/views.py 中实现登录接口、商品列表接口和创建订单接口:
from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework_simplejwt.tokens import RefreshToken from .models import Product, Order from .serializers import ProductSerializer, OrderSerializer def get_tokens_for_user(user): refresh = RefreshToken.for_user(user) return { 'refresh': str(refresh), 'access': str(refresh.access_token), } class LoginView(APIView): def post(self, request): username = request.data.get('username') password = request.data.get('password') from django.contrib.auth import authenticate user = authenticate(username=username, password=password) if user is None: return Response({'code': 1, 'msg': '用户名或密码错误'}, status=400) return Response({'code': 0, 'data': get_tokens_for_user(user)}) class ProductListView(APIView): def get(self, request): products = Product.objects.all() serializer = ProductSerializer(products, many=True) return Response({'code': 0, 'data': serializer.data}) class OrderCreateView(APIView): permission_classes = [IsAuthenticated] def post(self, request): serializer = OrderSerializer(data=request.data, context={'request': request}) if serializer.is_valid(): order = serializer.save() return Response({'code': 0, 'data': OrderSerializer(order).data}) return Response({'code': 1, 'msg': serializer.errors}, status=400)路由在 shop/urls.py 和项目根 urls.py 中注册。实际做项目时,我会把 auth 相关的 jwt 刷新接口直接用 simplejwt 自带的路由,省去自己实现。同时,为了让 Jmeter 测试登录后拿到 token 更顺手,登录接口返回结构必须是固定的 JSON 格式,不能随意改动字段名。
2.4 本地起服务并快速自测
写完视图和路由后,先本地把服务跑起来,确认基本功能正常,再去配置 Jmeter。启动命令是:
python manage.py runserver 0.0.0.0:8000这一步我会先用 curl 做一次最小验证。比如登录接口:
curl -X POST http://127.0.0.1:8000/api/login/ -H "Content-Type: application/json" -d '{"username":"admin","password":"123456"}'如果返回 JSON 里有 access 字段,就说明登录接口通了。商品列表接口用 GET 请求直接访问,确认能拿到商品数组。自测阶段建议确认三件事:第一,HTTP 状态码是否正确;第二,返回体里是否包含关键字段;第三,错误参数是否能返回统一错误结构。这三件事确认好,再进 Jmeter 时能少走很多弯路。
3. Jmeter 端测试计划设计与参数配置
3.1 Jmeter 安装与关键组件认知
Jmeter 是 Java 编写的工具,使用前需要确保本机装了 JDK,建议用 JDK 11 或 17。下载 Jmeter 后解压,进入 bin 目录,执行./jmeter启动图形界面。首次打开可能会看到一堆菜单选项,让人有点懵,但其实只需要关注几个核心组件:测试计划、线程组、取样器、监听器、配置元件、断言。
从功能上看,Jmeter 的组件可以这样理解:线程组相当于一群虚拟用户;取样器是具体发的请求,比如 HTTP 请求;配置元件是全局的参数设置,比如 HTTP 请求默认值、CSV 数据文件;断言用于判断响应是否符合预期;监听器负责收集和展示结果。一个完整的测试计划,就是把这些组件按层次组织在一起。
3.2 线程组与 HTTP 请求默认值设置
新建测试计划后,第一步是添加线程组。右键测试计划,选择添加 -> 线程 -> 线程组。线程组里最重要的三个参数是线程数、Ramp-Up 周期、循环次数。线程数表示同时模拟多少个用户,Ramp-Up 周期表示在多少秒内启动这些线程,循环次数表示每个线程执行几遍测试。第一次做功能测试时,我习惯把线程数设为 1,循环次数设为 1,先把单条请求调通。
接下来是 HTTP 请求默认值。这个组件特别适合多接口测试,因为它可以把共同的协议、域名、端口集中配置。右键线程组,选择添加 -> 配置元件 -> HTTP 请求默认值,填入服务器名称或 IP,比如 127.0.0.1,端口填 8000,协议填 http。这样后面添加多个 HTTP 请求时,只需要填路径和参数,不用每个取样器都重复写一遍域名。这个设置能大幅减少配置工作量,也避免因为某个请求漏填域名而测到错误地址。
3.3 接口参数化:CSV 数据集配置
接口测试不能只用固定的账号和商品数据,否则测不出边界问题。Jmeter 支持通过 CSV 数据集配置把外部文件中的数据读入测试计划。我先准备一个 test_users.csv 文件,每一行包含用户名、密码、预期结果三列:
admin,123456,0 testuser,wrongpass,1在 Jmeter 中右键线程组,添加 -> 配置元件 -> CSV 数据集配置。文件名填 CSV 的绝对路径,变量名称填 username,password,expect_code,分隔符用逗号,文件编码用 UTF-8。这样在 HTTP 请求里就可以用${username}和${password}引用当前行的数据。
这个过程的好处是,测试数据和测试脚本分离。修改测试数据不需要动 Jmeter 的脚本结构,而且同一个脚本可以用于不同环境、不同数据集的回归测试。比如我经常把线上环境的脱敏数据导出来,灌到 CSV 里直接跑一轮冒烟测试。
3.4 断言与结果监听器配置
没有断言的接口测试,只能算"发请求",不能算"验证接口"。Jmeter 中的断言组件里,最常用的是响应断言,它可以检查响应文本中是否包含指定的字符串。比如登录接口,我可以断言响应中是否出现"code": 0,如果出现就说明登录成功;再配合返回码断言,检查 HTTP 状态码是否为 200。
实际操作中,我还会加一个 JSON 断言。在较新的 Jmeter 版本里,添加 -> 断言 -> JSON 断言 可以直接用 JsonPath 表达式提取返回字段并校验。比如检查$.code是否等于 0,比字符串断言更精确,也不会因为字段顺序不同而误报。把多个断言叠加在同一个请求下,能对接口形成强校验。
结果监听器方面,最基础的是查看结果树和聚合报告。查看结果树可以看到每个请求的请求头、响应数据、断言结果,适合接口调试阶段使用。聚合报告则汇总了所有请求的平均响应时间、中位数、90% 响应时间、错误率、吞吐量,适合性能评估。我通常会在调试阶段打开查看结果树,在最终跑测试计划时把它禁用掉,只开聚合报告,因为查看结果树会占用大量内存,尤其是并发线程多的时候。
4. 登录令牌提取与接口关联实现
4.1 用 JSON 提取器拿到 token
实际业务中,订单创建这类接口需要携带用户登录后返回的令牌。在 Jmeter 里,可以用后置处理器把登录接口返回的 token 提取出来,传给后续请求。整个过程叫"关联",这一步做得好,测试计划才能真正串联起来。
在登录请求下面,右键添加 -> 后置处理器 -> JSON 提取器。由于登录接口返回的是{"code":0,"data":{"access":"...","refresh":"..."}},所以 JsonPath 表达式填$.data.access,变量名填access_token,匹配数量填 1。执行登录请求后,token 就会自动保存到access_token变量中,供其它请求引用。
这里有个实际经验:simplejwt 返回的 access token 默认有效期是 5 分钟,如果测试计划运行时间比较长,后续请求可能会遇到 401 认证失败。解决思路有两个,要么在 Jmeter 里增加一个"刷新 token"的请求,要么在 Django 端把 access token 的过期时间调长一点。开发环境我一般调长,测试环境会单独写刷新请求来模拟真实场景。
4.2 令牌传递与多接口串联测试
拿到 token 之后,需要把它放到 HTTP 请求头中。右键添加 -> 配置元件 -> HTTP 头管理器,然后新建一个请求头,名称填 Authorization,值填Bearer ${access_token}。注意 token 之间必须有空格,否则服务端解析请求头时可能报错。我用 Jmeter 时见过不少人忘了空格,导致接口一直 401。
配置好之后,订单创建请求的完整链路就是:登录请求 -> 提取 token -> 携带 token 创建订单。整个测试计划按顺序执行,就能模拟一个真实用户从登录到下单的完整操作。如果把线程数调成 50,再设置好 Ramp-Up 时间,就变成了 50 个用户同时走"登录->下单"流程,这种场景对发现接口并发问题特别有效。
4.3 并发场景下的注意事项
说到并发,Jmeter 模拟并发时有个经典陷阱:每个线程组里的线程会按照同样的逻辑执行请求,也就是说 50 个线程会各自独立地执行"登录->下单"链路,互不干扰。但订单表里 order_no 字段是唯一键,如果下单接口没有做唯一性处理,比如基于时间戳生成订单号,并发时容易碰撞,导致部分请求报唯一键冲突。所以我会在 Django 端用 uuid 生成订单号,保证并发场景下不会重复。
另外,Jmeter 默认每个虚拟用户不会自动使用不同的数据。只要你在 CSV 数据集配置里打开了共享模式设置,配置为"当前线程组"或"所有线程",每个线程读取到的数据行就会不同。如果不配置,多个线程可能读到同一行数据,导致重复提交。并发测试前,CSV 的数据行数最好大于线程数,避免数据被读空。
5. 常见问题与故障排查实录
5.1 403 与 CSRF 校验问题
Django 自带 CSRF 防护,会拦截 POST 请求中不带 CSRF token 的请求。用 Jmeter 直接发 POST 请求时,最容易撞到的问题就是 403 Forbidden。解决办法是,在接口视图上加上@csrf_exempt装饰器,或者在 Django 设置中配置 DRF 的认证方式时,把 CSRF 校验排除在接口路由之外。实际操作中,我倾向于在接口路由层面统一处理,避免每个视图都添加装饰器,显得很啰嗦。
如果测试的是已有项目,不想改动后端代码,也可以在 Jmeter 中先发一个 GET 请求拿到 CSRF cookie,再通过 Cookie 管理器把请求带过去,但这种方式麻烦且没必要。我的建议是:纯 JSON API 的 POST 请求,直接在 Django 端关闭 CSRF,因为接口场景本身是给程序调用的,不是给浏览器表单用的。
5.2 连接拒绝、超时与编码问题
Jmeter 报 Connection refused,大概率是服务没启动或端口不对。先确认 Django runserver 进程还在,再确认 HTTP 请求默认值里的端口与 runserver 的端口一致。还有种情况是 Jmeter 所在机器和 Django 服务不在同一台机器上,此时服务器 IP 不能写成 127.0.0.1,要写成 Django 服务所在机器的局域网 IP,并且 Django 启动时要监听 0.0.0.0。
响应中文乱码是另一个常见问题。Django 返回的 JSON 如果包含中文,Jmeter 查看结果树时可能显示成乱码。解决办法是在 Jmeter 的 jmeter.properties 或 user.properties 中,把默认编码改为 UTF-8。不过更稳妥的做法是在接口返回时,让 DRF 统一输出 Unicode 转义或者确保响应头包含charset=utf-8。我在实际项目里会直接设置 DRF 的 renderer 类,确保所有响应都是 UTF-8 编码。
5.3 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 403 Forbidden | CSRF 校验未关闭 | 在视图或路由层排除 CSRF |
| 401 Unauthorized | token 缺失或过期 | 检查 HTTP 头管理器中的 Authorization 格式 |
| Connection refused | 服务未启动或端口错误 | 确认 Django 服务状态和端口配置 |
| 中文乱码 | 编码未统一为 UTF-8 | 调整 Jmeter 默认编码或 DRF 响应头 |
| 断言失败 | 返回结构变化或字段名不匹配 | 查看结果树中实际响应,修改断言表达式 |
| 并发下单报错 | 订单号重复 | 后端改用 uuid 生成订单号 |
这张表基本覆盖了我做接口测试时遇到的绝大多数问题。有些问题看起来是 Jmeter 配置的问题,实际是后端接口设计没有考虑测试场景。开发接口时如果多想想"测试工具会怎么调用我",很多坑都能在源头避开。
5.4 调试前的排查思路
遇到接口测试异常时,我有一套固定的排查顺序,能省下不少时间。第一步,打开查看结果树,看响应体里到底返回了什么,很多问题一眼就能看出来。第二步,用 curl 直接请求同一接口,排除 Jmeter 侧干扰。如果 curl 正常而 Jmeter 异常,问题多半在请求头、环境变量或编码上;如果 curl 也异常,那就是后端代码的问题。第三步,检查 Jmeter 日志文件,这个文件记录了所有取样器的错误明细,比界面上的提示更完整。
这套排查思路看起来简单,但实际操作中很多人会跳着来,直接改代码改配置,结果越改越乱。按顺序排查,至少能保证每次只改一个变量,出问题也知道是哪个改动引起的。
6. 进阶扩展:测试数据准备与性能测试参数调优
6.1 测试数据准备策略
做接口测试时,测试数据往往比测试脚本更花时间。对于 Django 接口,我建议直接用 Django 的 Fixture 或者 ORM 批量生成测试数据。可以在应用目录下写一个 management command,批量创建几百条商品记录和测试账号,这样 Jmeter 压测时才有足够的数据可以查。
比如创建一个 Django management command,用 Faker 库生成 1000 条商品名称随机数据。这个命令可以重复执行,不会因为数据重复而报错。有了批量数据后,商品列表接口的分页、排序、搜索功能才能真正测到位。没有数据支撑的接口测试,拿到手的结果意义很有限。
6.2 性能测试参数调优
性能测试的核心是找到接口的瓶颈,而不是追求某个固定的并发数。先把线程数设成 10,跑一轮,看聚合报告里的平均响应时间和错误率;再逐步增加到 50、100,观察曲线变化。如果响应时间随并发数线性上涨且错误率上升,基本可以判断接口存在瓶颈,比如数据库连接池不够、查询没有走索引等。
Jmeter 里有一个很有用的指标叫吞吐量,单位是每秒请求数。这个指标能直观地反映系统处理能力。需要注意的是,吞吐量不是越高越好,而是要结合响应时间和错误率一起看。如果吞吐量上去了,但响应时间劣化严重,用户体验会很差。性能测试的结论,永远要综合三个指标来看,单看哪个都容易误判。
6.3 对 Django 端的反向优化建议
Jmeter 测出来的性能问题,很多可以反推回 Django 端优化。比如商品列表接口响应慢,可以在 Django Debug Toolbar 里看执行的 SQL 数量,如果存在 N+1 查询,就用select_related或prefetch_related把关联查询合并。数据库索引缺失也是常见问题,对乘积WHERE查询的字段加索引,性能提升非常显著。
我通常的做法是:先用 Jmeter 跑出基线数据,然后在 Django 端做一次优化,再用 Jmeter 跑第二遍对比前后数据。这种"测试-优化-回归"的循环,是接口质量提升最有效的方式。Jmeter 不只是用来交差的,它能把优化的效果量化出来,让每一次改动都有数据支撑。
我个人在实际操作中的体会是,接口开发与测试是一件事的两面,开发时把可测性考虑进去,测试时把业务逻辑理解到位,两边配合才能把接口质量真正提上去。先把 CSV 参数化跑熟,再去做令牌关联和并发,Jmeter 的学习曲线并没有想象中那么陡。后续如果团队规模再大一点,还可以把 Jmeter 脚本接入 CI 流程,用命令行模式jmeter -n -t script.jmx -l result.jtl定时跑回归测试,把接口测试从手工操作变成自动化任务,那又是另一个值得记录的话题了。