做接口测试最怕遇到验证码,尤其登录接口一旦套上验证码,脚本就没法全自动跑起来。很多测试同学卡在这一步,明明Jmeter配置都对,就是过不了登录,最后只能手动填码,性能测试也没法做。这篇文章我就把这几年处理验证码登录接口的Jmeter方案完整梳理一遍,从最省事的绕过方案到OCR识别、从单机调试到并发场景,连同踩过的坑一起说清楚。
1. 验证码登录接口测试的整体设计思路
1.1 验证码在登录链路中的真实位置
先说业务逻辑。一个典型的验证码登录接口,请求链路大概是这样的:用户打开登录页,前端向后端请求一张验证码图片,后端生成图片同时把验证码文本存到Session或者Redis;用户输入账号、密码、验证码,点击登录,前端把这三个字段连同Cookie里的SessionID一起提交;后端拿用户提交的验证码和存储的验证码比对,一致才继续走账号密码校验。
在Jmeter里模拟这个流程,卡点就两个:第一个是拿到验证码图片并识别出文本,第二个是保证提交验证码时关联的Session和获取图片时是同一个。如果你只写一个HTTP请求直接POST登录,验证码必然是错的,因为压根没走获取验证码这一步。理解了这条链路,后面所有方案都是围绕“如何让Jmeter感知验证码”展开的。
1.2 测试方案选型:绕过、固定码、还是OCR
根据项目实际情况,验证码登录接口的Jmeter脚本有三种主流方案,我按优先级排序:
第一是先查后端有没有验证码开关,比如开发环境是否支持配置为固定验证码,或者接口是否可以传入万能验证码。很多公司的测试环境和预发环境是有后门的,直接关掉验证码或者固定成"0000",这是最稳定、识别率100%、并发压测完全不受影响的方案。
第二是OCR识别方案。后端验证码图片接口正常可用,Jmeter先请求图片,再用OCR工具识别文本,然后填入登录请求。这种方式适合验证码复杂度不高、图片背景干扰不大的场景,识别率能做到70%到90%。
第三是人工介入方案。脚本跑到登录步骤时暂停,测试人员肉眼看图片手动填码,填完再继续。这个方案我只在调试单个请求时用,压测基本不考虑。
额外提一句,有些系统用的是短信验证码,那需要和后端约定测试环境直接用固定短信码,或者接短信平台做回读,原理和图片验证码类似,只是获取验证码的通道不同。
1.3 为什么推荐优先走后门方案
很多测试同学有个误区,觉得用OCR识别验证码才显得专业。我的观点恰恰相反,压测的核心目标是验证登录接口在并发下的表现,而不是证明你能破解验证码。验证码只是登录流程里的一道锁,如果你能把锁卸掉,同样能验证登录逻辑,为什么非要拿钥匙慢慢开?
性能测试场景下每分钟可能要发起几百上千次登录,OCR识别一次平均要几百毫秒到一两秒,识别率又不稳定,一旦识别失败脚本就报错,测试结果里混入了大量假失败,最后分析报告时根本说不清楚到底是接口慢还是识别慢。所以我的建议是:能用固定码就用固定码,能关就关,实在不行再上OCR,而且OCR方案只适合功能测试或者小并发调试。
另外在写Jmeter脚本时,不管用哪种方案,都要把验证码的获取和提交设计成同一个线程组里的前后步骤,并且配合Cookie管理器保持会话上下文。如果你用两个线程组分别跑“获取验证码”和“登录”,Session不共享,等于白做。
2. 环境准备与Jmeter配置的核心细节
2.1 Jmeter版本和插件选型
做验证码登录接口测试,建议使用Jmeter 5.4以上版本,我用过5.1到5.6,5.4以后对JSON提取和断言支持更顺手,内置的JSON Extractor也足够用。安装路径不要有中文和空格,不然某些Java特性会踩坑。JDK我这边统一用的JDK 8,很多公司测试机还是老项目,JDK 11在个别环境会有兼容性问题。
需要准备的插件不多,主要是三个:
- Java Request Sampler或者直接用HTTP Request Sampler,这个是Jmeter自带的。
- BeanShell Sampler或者JSR223 Sampler,用于调用OCR接口和处理字符串。JSR223推荐用Groovy,性能比BeanShell好,但如果你不熟悉Groovy,BeanShell也够用。
- JSON Extractor和Regex Extractor,用来从响应里提取SessionID、图片Base64数据、登录返回的Token等。
如果你的项目用的是HTTPS,还需要处理证书。Jmeter自带的HTTP Client默认会校验SSL证书,如果被测系统是自签名证书,会报PKIX path building failed。我的做法是把被测系统的证书导出为CER文件,然后用keytool -importcert导入到Jmeter运行JDK的cacerts信任库,密码默认changeit。注意Jmeter的JRE和命令行用的JDK要是同一个,否则导入没生效。
2.2 线程组结构设计:从单用户调试到并发压测
验证码登录的Jmeter脚本,线程组的结构直接决定脚本能不能跑通。我的习惯是先做单线程调试,再改并发。线程组内部结构分为四块:
- HTTP Cookie Manager,放在线程组最上面,全局生效,用于维护Session。
- 获取验证码的HTTP Sampler,对应后端验证码图片接口。
- OCR识别层,用JSR223或者BeanShell调用外部OCR接口,把识别结果存到变量。
- 登录的HTTP Sampler,引用账号、密码和验证码变量,提交后通过断言判断是否登录成功。
如果你要用CSV文件参数化账号密码,就在线程组上加CSV Data Set Config,注意把“分享模式”改成“所有线程”,否则多线程下每个线程只会读到第一行数据。
单线程调试时循环次数设为1,监听器加一个View Results Tree就够了。压测时循环次数按需求调整,同时把监听器精简掉,避免监听器本身成为瓶颈影响测试结果。
2.3 证书和HTTPS脚本录制的前置处理
有些项目连验证码图片接口也是HTTPS,而压测环境又是HTTPS证书做的双向认证,这种就麻烦一点。Jmeter录制HTTPS脚本时还要生成一个代理证书,但日常测试不需要录制,直接用HTTP Request手写接口参数就行,注意在HTTP Request的Implementation里选HttpClient4,并勾选Use KeepAlive。
遇到过几次的报错是Connect to ... timed out,排查后发现是JDK的TLS版本和被测服务器不匹配。可以在jmeter.properties里调整https.default.protocol=TLSv1.2,同时设置httpclient4.retrycount=0,避免网络抖动时Jmeter反复重试造成请求堆积。
如果你用的是自签名证书且不想导入信任库,也可以在HTTP Request的Advanced里勾选Use HTTP Client 4,然后在jmeter.properties里把server.rmi.ssl.disable这类参数改掉。但我要提醒,跳过SSL验证只适合你自己本机调试,压测时建议按正规方式导入证书,不然数据都不安全。
3. 固定验证码方案:最省事的登录接口联调方式
3.1 后端配置固定验证码的两种常见做法
固定验证码的后端实现方式通常有两种。第一种是后端代码写死了通用验证码,比如if (captcha.equals("0000")) { // 直接通过 },这种万能码逻辑一般只在测试环境保留,上线前会删掉。第二种是验证码生成后不存Redis而是存在一个全局配置类里,测试人员可以调接口给验证码赋一个固定值。
不管是哪种,前端都需要对应处理。如果前端拿不到图片,登录页显示不出来,那就换个思路:后端只对验证码接口做固定,前端正常请求图片,但无论图片显示什么,后端的固定码都不变,测试时直接输入固定码即可。如果前端还会自己校验一次验证码格式,比如固定码位数不足4位直接不允许提交,那就把固定码设计成符合格式要求的值,例如"8888"。
3.2 Jmeter脚本怎么写
固定验证码场景下,Jmeter脚本是最简单的。完整步骤如下:
第一步,开启Jmeter,Test Plan下添加一个线程组,命名“固定验证码登录调试”。线程组设置里线程数填1,Ramp-Up填1,循环次数填1。
第二步,在线程组下添加Config Element里的HTTP Cookie Manager。这里需要注意,Cookie Manager默认会自动管理请求返回的Set-Cookie,建议勾选Clear cookies each iteration,否则多次循环时Session可能串掉。
第三步,添加一个HTTP Request,请求登录接口。协议HTTP或HTTPS,服务器名称填被测地址,端口看环境,方法POST,路径填登录URL。在Parameters或Body Data里添加三个字段:username、password、captcha。captcha直接填固定码,例如8888。
第四步,添加一个Response Assertion,断言登录成功接口返回的标志字段。比如返回JSON里包含"success":true或者"code":0。
第五步,点击运行,查看结果树。如果登录成功,说明固定码生效。然后你再改成10个线程循环100次跑压测,脚本不需要变,逻辑通畅。
这里给一个JSON断言示例,比如响应为:
{ "code": 0, "message": "success", "data": { "token": "abc123" } }可以在Response Assertion里选择JSON Path Assertion,如果用JSR223断言,可以用以下Groovy代码:
def response = new groovy.json.JsonSlurper().parse(prev.getResponseData()) if (response.code != 0) { AssertionResult.setFailure(true) AssertionResult.setFailureMessage("登录失败: " + response.message) }注意,如果登录接口的响应不是JSON而是文本,就用Contains断言检查关键字。很多老系统登录成功后会返回一个跳转URL或者一段HTML,里面包含用户名,这种直接Contains匹配更稳定。
3.3 固定码方案的局限和适用边界
固定码方案虽然省事,但不是所有项目都能用。如果你的被测系统是外部第三方提供的服务,比如对接了SSO登录、短信平台,或者后端逻辑写死不允许跳过验证码,那固定码就走不通。另外在安全性要求高的金融级项目里,验证码逻辑是硬编码在前端JS和后端服务两侧的,测试环境也不开后门,那只能走OCR识别路线。
另一个坑:有些系统虽然验证码是固定码,但后端还是会校验验证码和Session的绑定关系,比如验证码存入Redis时key是SessionID,提交登录时如果没有携带相同的SessionID,即使验证码对也不通过。这种情况下,即使固定码生效,你也必须在Jmeter脚本里先请求一次验证码图片接口获得SessionID,再携带这个SessionID去登录。所以固定码方案里仍然建议先跑一个获取验证码的请求,主要是为了拿到Session上下文。
4. 动态验证码的OCR识别实战
4.1 动态验证码的获取与传递方式
当没有后门可用时,只能走真实接口流程。动态验证码图片接口的响应形式一般有三种:
第一种是返回一张图片流,Content-Type是image/jpeg或image/png,响应体是二进制图片。Jmeter直接显示乱码,需要把它保存为文件再识别。
第二种是返回JSON,里面包含Base64编码的图片字符串,类似{"img":"data:image/png;base64,iVBORw0KGgo..."},同时返回一个captchaId或者uuid。这种情况需要先用JSON Extractor提取Base64字段,再解码成图片文件。
第三种是验证码不在图片里,而是通过短信或者邮箱发送,这种接口通常是一个发送验证码的请求,然后测试人员需要去数据库或测试账号里查最新验证码。把这部分也纳入自动化的话,需要额外查询能力,不在本文重点范围。
不管哪种形式,核心流程都一样:获取图片 -> 本地保存图片 -> OCR识别 -> 提交登录。Jmeter本身没有图片识别能力,所以我的做法是让Jmeter调用一个本地的OCR服务,或者用BeanShell调用Java OCR库。
4.2 本地OCR服务搭建:Python + PaddleOCR最短路径
我用的最多的OCR方案是PaddleOCR,识别效果比Tesseract好,尤其是带扭曲和干扰线的验证码。部署方式很简单,在本地或一台压测辅助机上装Python 3.8以上环境,然后安装:
pip install paddlepaddle paddleocr使用PaddleOCR 2.6以上版本,可以写一个简单的HTTP服务,接收验证码图片路径或Base64字符串,返回识别结果。参考代码如下:
from paddleocr import PaddleOCR from flask import Flask, request, jsonify import base64 import tempfile import os app = Flask(__name__) ocr = PaddleOCR(use_angle_cls=True, lang='en', show_log=False) @app.route('/ocr', methods=['POST']) def ocr_api(): data = request.get_json() img_b64 = data.get('image') if not img_b64: return jsonify({'code': 400, 'msg': 'no image'}) tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False) tmp.write(base64.b64decode(img_b64)) tmp.close() result = ocr.ocr(tmp.name, cls=True) os.unlink(tmp.name) text = '' if result and result[0]: for line in result[0]: text += line[1][0] # 清理非字母数字字符,验证码一般只包含字母数字 text = ''.join(ch for ch in text if ch.isalnum()) return jsonify({'code': 0, 'text': text}) if __name__ == '__main__': app.run(host='0.0.0.0', port=9898)这样Jmeter只需要发送一个HTTP请求到http://localhost:9898/ocr,把Base64图片传过去,OCR服务返回识别文本,Jmeter再把这个文本作为登录参数即可。把OCR服务和被测服务分开部署,避免OCR计算影响压测机的请求发送能力。
Tesseract我也用过,优点是安装简单,Windows下有现成的exe,但识别效果对验证码噪声敏感,需要做灰度化、二值化预处理,否则识别率感人。如果验证码风格简单,纯数字四位,用Tesseract也够。PaddleOCR的好处是不需要自己处理图像增强,模型自带抗干扰能力,识别率明显高一截。
4.3 Jmeter调用OCR服务的完整脚本结构
以下是我在Jmeter里跑通的完整脚本结构,直接照着搭就能用。
线程组结构:
- HTTP Cookie Manager
- 请求验证码图片接口(HTTP Sampler)
- 提取Base64图片数据(JSON Extractor或正则提取器)
- 调用OCR识别服务(HTTP Sampler)
- 提取OCR返回的识别文本(JSON Extractor)
- 登录接口(HTTP Sampler)
- 登录结果断言(JSR223或JSON断言)
第一步,验证码图片接口通常返回JSON,形如:
{"captchaId":"a1b2c3","imgBase64":"data:image/png;base64,XXXX"}在HTTP Sampler上添加JSON Extractor:
- Variable names:
captchaId,imgBase64 - JSON Path expressions:
$.captchaId,$.imgBase64 - Match Numbers: 1,1
- Default Values: NOT_FOUND
第二步,添加一个BeanShell Sampler或JSR223 Sampler,把Base64字符串处理成纯Base64数据。因为接口返回的imgBase64可能带着data:image/png;base64,前缀,需要用正则去掉。在JSR223 Sampler里写Groovy:
import java.util.regex.Pattern String raw = vars.get("imgBase64") String clean = raw.replaceAll("^data:image/\\w+;base64,", "") vars.put("imgPure", clean)这段代码会把data:image/png;base64,...转成纯Base64,存到imgPure变量,OCR接口那边只认纯Base64。
第三步,添加调用OCR服务的HTTP Sampler:
- 协议: http
- 服务器: localhost
- 端口: 9898
- 路径: /ocr
- 方法: POST
- Body Data:
{"image":"${imgPure}"}
别忘了添加HTTP Header Manager,设置Content-Type: application/json。
第四步,在OCR请求下添加JSON Extractor提取返回文本:
- Variable names:
ocrText - JSON Path expressions:
$.text - Default Values: OCR_FAIL
第五步,登录接口引用这个变量:
- 参数名: captcha
- 值:
${ocrText}
第六步,运行后观察。如果OCR识别失败,登录接口自然会返回验证码错误,这时需要检查OCR服务的日志输出,看看识别文本是否为空或明显错误。
4.4 识别结果的清洗和后置处理
OCR识别结果经常带着空格、换行、特殊字符,比如识别出aB 3d,或者0被识别成O,1被识别成l。后端验证码通常不区分大小写,一般会统一转小写。Jmeter端在提交前最好做一层清洗:
- 转小写:
vars.put("ocrText", vars.get("ocrText").toLowerCase()) - 去空格:
vars.get("ocrText").replaceAll("\\s+", "") - 只保留字母数字:
.replaceAll("[^a-z0-9]", "")
在Groovy里可以一次搞定:
String text = vars.get("ocrText").toLowerCase().replaceAll("[^a-z0-9]", "") vars.put("ocrText", text)清洗好后可以做一次有效性判断,如果清洗后的长度和验证码期望长度不符(比如验证码是4位,清洗后只有3位),直接断言失败并跳过登录请求,免得提交无意义的登录请求污染测试数据。
这里有个小技巧,如果你知道验证码位数,可以在JSR223 Sampler里提前判断:
String text = vars.get("ocrText") if (text.length() < 4) { prev.setStopThread(false) AssertionResult.setFailure(true) AssertionResult.setFailureMessage("OCR识别结果过短: " + text) }注意prev对象是SampleResult,用prev.setStopThread(false)不会停止线程,但可以标记失败。如果你希望识别失败时直接跳过后续登录请求,可以用vars.put("ocrValid", "false"),然后在登录接口的Action to be taken里配置条件判断,不过这种逻辑在Jmeter里稍微绕,我一般宁可让它登录失败,然后依赖断言统计识别失败率。
4.5 OCR识别方案的并发注意事项
OCR识别方案做并发时最容易翻车。之前我在一个压测项目里用4台施压机,每台机器并发50个线程,OCR服务部署在单独一台机器上,结果还是把OCR服务打崩了。原因很简单:50个并发用户同时请求验证码图片,然后一齐请求OCR识别,OCR模型推理是计算密集型的,单线程处理能力有限,请求全部堆积,超时率飙升。
解决思路有两种:
第一种是OCR服务加队列和并发控制,限制同一时刻推理的请求数,多余请求排队等待。如果你会改Flask服务的话,可以用Celery或者简单的线程池。我的做法是在Flask前加一层信号量:
import threading semaphore = threading.Semaphore(4) # 同时最多4个OCR推理 @app.route('/ocr', methods=['POST']) def ocr_api(): with semaphore: # 推理逻辑 pass第二种是在Jmeter侧控制并发识别请求的速率,比如用Constant Throughput Timer限制每分钟的OCR请求数,或者用JSR223 Sampler加个Thread.sleep随机等待。这也能降低识别请求的瞬时冲击。
另外,OCR识别耗时通常在300ms到1500ms之间,如果登录接口压测的目标吞吐是100 QPS,那就意味着OCR服务至少要扛住每秒100次识别。这种情况不要用单机Flask+PaddleOCR,建议用GPU机器或者独立OCR集群。不过说实话,真到了这种量级,你还是强烈建议后端提供固定码开关,否则压测就变成了OCR服务压测,失去了登录接口压测的意义。
5. 登录结果断言与核心参数传递
5.1 登录接口的返回数据怎么校验
验证码对了之后,登录接口会返回一堆东西,常见的有token、用户信息、权限菜单、登录时间等。测试脚本里不需要校验全部字段,重点就两个:登录是否成功、拿到的凭证是否有效。
对一个返回JSON的登录接口,我会用JSON Extractor提取两个值:登录状态字段和token字段。比如:
{ "code": 0, "msg": "ok", "data": { "token": "eyJhbGciOi...", "userName": "张三" } }在登录HTTP Sampler下添加JSON Extractor:
- Variable names:
loginCode,authToken - JSON Path expressions:
$.code,$.data.token
然后在响应断言或JSR223断言里判断loginCode是否为0,token长度是否大于20。这样比单纯判断响应代码更可靠,因为有些接口即使登录失败,HTTP状态码仍然是200。
5.2 BeanShell断言的实际写法
评论区问得最多的是beanshell断言怎么判断登录结果。这里给一个通用模板,注意BeanShell和JSR223的用法有些差异。我推荐用JSR223 + Groovy,因为BeanShell性能实在差,不过在5.4版本里BeanShell还是兼容的。
JSR223断言示例:
import groovy.json.JsonSlurper def responseText = prev.getResponseDataAsString() def json = new JsonSlurper().parseText(responseText) if (json.code != 0) { AssertionResult.setFailure(true) AssertionResult.setFailureMessage("登录接口返回错误: " + json.msg) } else { if (json.data.token == null || json.data.token.length() < 20) { AssertionResult.setFailure(true) AssertionResult.setFailureMessage("Token异常: " + json.data.token) } }如果你必须在BeanShell里写,那就用org.apache.jmeter.assertions.AssertionResult,写法类似,只是解析JSON需要用org.json.JSONObject或者自己写正则。BeanShell多线程下容易阻塞,压测时尽量别用。
5.3 登录Token如何传递给后续接口
登录接口测通了,往往还有后续业务接口需要带Token,比如查询订单、提交表单。在Jmeter里传递Token有三种方式:
第一种是正则提取器,登录响应里如果Token是明文,用正则抓取。比如:
"token":"([^"]+)"第二种是JSON Extractor,上面已经写过。FastJSON解析比正则靠谱,推荐优先用。
第三种是JSR223脚本存入属性,跨线程组使用。如果登录在一个线程组,后续业务在另一个线程组,那需要把Token存成全局属性。代码示例:
props.put("authToken", vars.get("authToken"))另一个线程组在HTTP Header Manager里引用:
Authorization: ${__P(authToken,)}这里注意,__P函数读取属性,${authToken}读的是变量,跨线程组必须用属性,还不能把这个逻辑用一个线程组里的循环来跑,否则属性被覆盖。
5.4 断言太多会影响压测结果吗
压力测试时断言尽量精简。有人喜欢在每个Sampler后面挂一堆断言检查各种字段,这在高并发下会放大Jmeter的CPU开销,导致施压机自身成为瓶颈。我的习惯是压测脚本只保留关键断言:登录接口一个,核心业务接口一个。调试阶段可以多断言,压测时把非必要的断言全部注释掉或者放到setUp Thread Group里做前置校验。
另外压测时不要加View Results Tree这种全量结果监听器,它会存储每个Sample的响应体,内存增长飞快。建议用Simple Data Writer输出到文件,或者只启用Summary Report,顺带定时清理结果文件。
6. 常见问题与排查技巧实录
6.1 提示验证码错误,但OCR识别看起来是对的
这个问题遇到太多次了。原因一般有三个:Session不一致、大小写问题、验证码过期时间太短。
Session不一致最常见。Jmeter里获取验证码的HTTP请求和登录的HTTP请求虽然都在同一个线程组,但如果线程组配置了Reset cookies each iteration,或者Cookie Manager没有放在线程组最上层,就会导致两次请求的SessionID不同。后端存验证码时按SessionID关联,Session不一致怎么提交都失败。解决方法是把HTTP Cookie Manager放在线程组首个子节点,并确认两次请求都携带了同一个SessionID。你可以在查看结果树里看登录请求的Cookie请求头,和获取验证码请求的Cookie头比对一下。
大小写问题。后端通常把验证码转成小写再比对,但OCR识别出来可能混着大小写,如果你在提交前没有统一转小写,能识别对但验证失败。清洗逻辑里强制toLowerCase()能解决。
验证码过期时间太短。有些系统验证码有效期只有30秒,如果从获取图片到识别再登录用了很久,尤其OCR服务响应慢,后端已经删掉这验证码了。日志里看到获取验证码时间戳和登录时间戳差了几十秒就要排查到底哪一步慢了。
6.2 登录接口报HTTPS握手失败
压测环境是HTTPS时,Jmeter经常报SSL handshake或者PKIX path错误。主要原因是Jmeter所用JDK的信任库没有导入被测系统证书,或者被测系统TLS版本和JDK默认协议不一致。
排查步骤:
- 用浏览器打开登录接口,点击地址栏锁图标,导出证书为cer文件。
- 找到Jmeter的JDK路径,执行:
keytool -importcert -file your.cer -keystore cacerts -alias youralias - 如果提示密码,默认是
changeit。 - 如果导入后还是握手失败,检查
jmeter.properties里的https.default.protocol,改成TLSv1.2。 - 被测系统如果只支持TLS1.3,那就需要升级JDK版本,JDK8默认支持到TLS1.3需要打个补丁,比较麻烦。这种情况建议换更高版本JDK来跑Jmeter。
6.3 并发时获取验证码失败率突然上升
并发环境下获取验证码接口本身也可能挂。如果响应超时,先看是不是验证码接口有频率限制,比如同一IP每秒只允许请求一次,那并发必挂。这种限制通常可以通过后端配置关闭,或者在压测机前加一层代理统一出口IP。
如果验证码接口本身不稳定,比如返回500或空值,那即使Jmeter脚本正确,登录也过不去。要判断是脚本问题还是接口问题,单独用一个线程组只循环请求验证码接口,看单接口的表现。如果是接口性能问题,需要先解决接口本身,再谈登录压测。
6.4 常见问题速查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 登录永远提示验证码错误 | Cookie/Session不一致 | 检查Cookie Manager位置和Cookie内容 |
| 登录永远提示验证码错误 | 验证码大小写问题 | OCR结果转小写后提交 |
| OCR识别总是空 | 图片Base64前缀没去掉 | 用正则去掉data:image前缀 |
| OCR识别总是空 | OCR服务未启动 | 单独用Postman调一下OCR接口 |
| OCR识别结果长度不对 | 模型识别出多余字符 | 用正则只保留字母数字 |
| HTTPS握手失败 | JDK信任库没导入证书 | keytool导入证书 |
| HTTPS握手失败 | TLS版本不一致 | 调整jmeter.properties |
| 并发时大量超时 | OCR服务处理不过来 | OCR服务加并发限制或换GPU |
| 并发时大量超时 | 验证码接口限流 | 关闭限流或改压测策略 |
| 线程组获取验证码和登录Session不一致 | 线程组循环清Cookie | 勾选Cookie Manager选项 |
| 登录成功但断言失败 | 断言字段路径错误 | 在结果树里看实际返回JSON结构 |
6.5 调试时最实用的三个工具
除了Jmeter,我调试这类接口还会用Postman和在线Base64工具。Postman里可以快速手动走一遍获取验证码、登录的流程,确认接口逻辑没问题再写Jmeter脚本。Base64工具用于检查图片数据是否完整,如果Jmeter提取的Base64字段在工具里能正常显示图片,说明提取没问题,问题出在识别环节。
还有一个技巧:在Jmeter脚本里临时加一个Debug Sampler,把需要的变量打印到结果树里。排查时常用,调试完记得删掉,否则压测报告里打印的变量数据量很大,影响结果文件大小。
7. 从功能调试到压测落地的几个实操心得
7.1 脚本调试时的“三色原则”
我调试Jmeter脚本时,习惯只关注结果树里红绿灯:绿色是成功,红色是失败,灰色是跳过。固定验证码方案下,整个脚本应该是全绿;OCR方案下,只要登录断言不失败就说明整个链路通了。
如果登录请求变红,第一时间不是去看OCR服务,而是先看获取验证码请求是否绿色,再看OCR请求是否绿色,最后看登录请求的请求体里验证码字段是否有值。按照这个顺序排查,能省很多时间。
7.2 压测前的试运行清单
正式压测前,建议按这个顺序做一遍:
- 单线程循环1次,确认全链路绿色。
- 单线程循环5次,确认每次的验证码都能识别成功,没有偶发失败。
- 10个线程循环10次,观察OCR服务CPU和响应时间,确认扛得住。
- 关掉调试监听器,打开Summary Report,开始正式压测。
这一步特别重要,很多人在第2步就开始并发压测,结果全是验证码识别失败,白白浪费压测时间。识别率不够高的情况下,先在低并发把OCR服务调优,或者改用固定码。
7.3 关于验证码识别率的一句话经验
PaddleOCR默认模型对印刷体字符识别很好,但验证码常常故意扭曲、加干扰线、字符粘连。如果识别率低于70%,别死磕模型,先去看验证码生成逻辑:如果验证码固定4位数字且无复杂干扰,PaddleOCR轻松90%以上;如果带彩色噪点和弧线,识别率可能掉到60%,这时候跟产品经理反映一下,测试环境降低验证码复杂度,是双方都省事的办法。
7.4 最后一点:压测报告的解读别被“识别失败”污染
用OCR方案做完压测,分析报告时要把识别失败导致的接口错误独立统计。我的做法是在登录接口的断言里区分两类失败:一类是断言验证码错误,一类是断言超时。这样报告里能直接看验证码识别失败率,而不是笼统的登录失败率。否则领导看到一片红,第一反应就是登录接口有问题,实际上很多是OCR识别不准造成的。
我个人在实际项目里的取舍是:只要能拿到测试后端配置权限,验证码一律用固定码或者关闭验证码;只有拿不到权限、必须验证整个登录链路时,才启动OCR辅助识别。而且要记得,验证码识别本身是个概率问题,不管怎么优化都不可能做到100%准确,压测场景下要保证的是系统稳定性,不是保证每张验证码都认识。先把这两件事分清楚,Jmeter验证码登录的脚本你就基本琢磨透了。