ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Jmeter接口自动化实战:图片验证码OCR识别与处理方案

Jmeter接口自动化实战:图片验证码OCR识别与处理方案 做接口测试最怕什么不是参数写错了也不是断言不会写而是辛辛苦苦把登录、注册、下单的流程捋顺了脚本却在“图片验证码”这一步卡住。手工填码还能跑一旦要批量压测或者跑回归人工识别验证码根本不现实。Jmeter本身不带图像识别能力所以“怎么让Jmeter自动处理图片验证码”就成了接口自动化绕不开的一个坎。这篇文章就把我实际处理图片验证码的完整思路写下来。包括验证码接口的常见返回格式、怎么把图片从接口里取出来、Jmeter里做OCR识别、识别结果回填、以及压测场景下的降级方案。如果你正在用Jmeter做接口测试正好撞上图片验证码这个拦路虎又不想直接放弃自动化那这篇文章可以直接照着落地。1. 图片验证码为什么会成为接口测试的拦路虎1.1 接口测试里常见的验证码类型先说清楚一个事实图片验证码不是一个东西至少分好几类。很多人在网上搜“Jmeter图片验证码处理”上来就想着OCR识别其实先把验证码的类型分清楚你才知道方案是不是可行。我遇到过的图片验证码大致有这么几类验证码类型特征自动化难度纯数字/字母4-6位字符可能有轻微扭曲低OCR基本能解决运算式验证码显示“35?”或“a1b2c3”中需要额外解析逻辑扭曲干扰线背景噪声、字符扭曲严重高OCR识别率不稳定滑块/拼图验证需要拖动拼图到指定位置很高Jmeter几乎没法模拟点选文字/图形按提示点击图中的某个字或物体很高通常要接视觉模型或打码平台接口测试里最常遇到的是前三种尤其是登录接口、注册接口、发送短信验证码接口几乎清一色是数字字母验证码。这种验证码的服务端实现一般比较简单生成一个随机字符串画到图片上然后把正确答案存到服务端会话里图片通过接口返回给前端。我们要做的就是把这个图片拿到手、识别出字符串、在过期之前提交给登录接口。1.2 服务端是怎么校验图片验证码的理解校验原理很重要否则你容易把验证码处理想得太简单或者想得太复杂。典型流程是前端请求验证码图片接口比如GET /captcha服务端生成图片同时把正确答案比如8f3a存到Session或RedisKey通常是会话ID或前端传来的唯一标识服务端把图片以image/png或base64字符串的形式返回给前端用户把图片里的字符填到输入框前端把“验证码”和“会话标识”一起提交给登录接口服务端拿提交的验证码和Session/Redis里存的答案做比对一致才放行。这里有三个关键信息验证码图片本身、会话标识通常是Cookie里的JSESSIONID、提交的验证码值。你在Jmeter里要做的就是完整复刻这个流程而不是单纯识别图片。很多新手只盯着“识别图片”这一步忽略了对Cookie的管理结果识别对了依然登录失败白白浪费时间。1.3 所以处理图片验证码的实质是什么说白了图片验证码就是服务端故意设置的一场“图灵测试”利用人类和机器在图像识别能力上的差异来拦截自动化请求。而我们要做的是让Jmeter具备“看图识字”的能力并且抢在验证码过期前把它提交回去。这就引出了处理图片验证码的核心四步取图从接口拿到验证码图片可能是base64、文件流、URL处理对图片做灰度化、二值化等预处理提升机器识别准确率识别用OCR引擎把图片中的字符转成文本回填把识别结果作为参数提交到目标接口同时维护好Cookie会话。这篇文章后面所有内容都是围绕这四步展开的。2. 动手之前先做方案取舍OCR不是唯一解很多人在处理图片验证码时第一反应就是“上OCR”但以我踩过的坑来看OCR应该是你最后一个选择而不是第一个。动手之前先把下面三条路线想清楚。2.1 路线A测试环境后门——性价比最高的方案所谓后门就是请开发在测试环境的验证码上开个口子。常见的做法有两种万能验证码开发在登录校验逻辑里加一个判断比如验证码等于8888时就放行关闭校验测试环境直接跳过验证码校验逻辑。这条路线的优点极其明显稳定、快、不依赖识别率。缺点是需要开发配合而且只适用于测试环境。有些团队会有顾虑觉得“加后门会不会影响安全性”实际上只要限制在测试环境、走配置开关而不是写死在代码里风险是可以接受的。我强烈建议做接口自动化之前先跟开发沟通能不能提供万能验证码。如果能这篇文章后面的内容你只需要看压测和Cookie那部分就够了因为识别工作根本不需要做。2.2 路线B本地OCR识别——最通用的方案如果后门拿不到比如验证码是第三方厂商提供的、或者开发不愿改代码那就只能走识别路线了。本地OCR识别的核心工具是开源的Tesseract配合OpenCV做图片预处理。它的优点是免费、离线、可控缺点是识别率受图片质量影响很大——有些验证码做得特别“变态”扭曲加噪点加干扰线Tesseract识别率可能只有50%。这种情况下你就需要做大量预处理调参。Jmeter本身没有OCR能力所以通常的做法是在Jmeter的JSR223 Sampler里直接调用Java版的OCR库比如tess4j或者把图片发到本地搭建的OCR识别服务让服务把结果返回给Jmeter。这两种方式我在第4部分会详细给代码。2.3 路线C打码平台——复杂验证码的兜底方案对于滑块拼图、点选文字这类复杂验证码Tesseract基本无能为力这时候很多团队会接入打码平台。打码平台的原理是把验证码图片发给平台的真人/AI识别再把答案返回给你。优点是识别率高、支持类型多缺点是按次计费、依赖网络请求而且从合规角度讲你没法控制平台那边拿你的验证码图片去做什么。所以这类方案通常只建议用在实在搞不定的场景并且要提前确认平台的数据处理合规性。2.4 我实际选择的是哪种组合我目前的策略是先谈后门后门谈不下来就用OCROCR搞不定再评估打码平台。为什么把OCR放在第二位而不是去打码平台因为接口测试里出现的验证码绝大多数还是数字和字母远没到需要打码平台出手的程度。而且OCR对Jmeter来说是无状态的本机操作不引入额外的网络依赖跑压测的时候也更好控制。如果你现在刚开始做我建议你也按这个顺序来。先记住一句话验证码处理的目的是让自动化流程稳定跑通不是为了表演识别技术。把精力放在识别上之前先确认这条路是不是非走不可。3. Jmeter获取验证码图片的几种姿势确认要走OCR路线之后第一件事就是把验证码图片从接口里拿出来。这一步看起来简单但有一个容易踩坑的点不同系统的验证码接口返回格式不一样处理方式完全不同。3.1 场景一接口返回JSON里的base64字符串这是最常见的格式。服务端返回类似这样的JSON{ code: 0, data: { captchaId: f0a0b1c2-..., captchaImg: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA... } }处理方式分两步。第一步用JSON Extractor或JSR223脚本把captchaImg字段取出来。我比较懒直接用JSON Extractor变量名命名为captchaBase64。注意有些接口base64里带data:image/png;base64,前缀有些不带后面识别脚本里要做一个去前缀的判断防止解码失败。第二步把base64字符串解码成图片文件。这一步必须用JSR223 Sampler里写Groovy脚本完成因为Jmeter自带的__base64Decode函数只能解出字节数组不能直接落盘成图片。import java.util.Base64 String base64Str vars.get(captchaBase64) if (base64Str null || base64Str.isEmpty()) { throw new Exception(captchaBase64 变量为空请检查JSON提取器配置) } if (base64Str.contains(,)) { base64Str base64Str.substring(base64Str.indexOf(,) 1) } byte[] imgBytes Base64.getDecoder().decode(base64Str) String filePath D:/captcha_temp/captcha_ System.currentTimeMillis() .png FileOutputStream fos new FileOutputStream(filePath) fos.write(imgBytes) fos.close() vars.put(captchaFilePath, filePath) log.info(验证码图片已保存: filePath)这段脚本做的事很简单取变量、去前缀、解码、存文件。存文件不是为了看而是为了给后续的OCR引擎提供输入。3.2 场景二接口直接返回图片文件流另一种常见格式是验证码接口直接返回image/png或image/jpeg的二进制图片浏览器里直接打开验证码接口就能看到一张图。这种情况下你不需要JSON提取器但需要在HTTP请求下加一个BeanShell PostProcessor或JSR223 PostProcessor把响应数据保存成文件import java.io.FileOutputStream byte[] responseData prev.getResponseData() String filePath D:/captcha_temp/captcha_file_ System.currentTimeMillis() .png FileOutputStream fos new FileOutputStream(filePath) fos.write(responseData) fos.close() vars.put(captchaFilePath, filePath)这里用prev对象获取上一次请求的响应字节。很多人把这个写在JSR223 Sampler里会报错是因为JSR223 Sampler里没有prev这个变量只有PostProcessor里才有。3.3 场景三接口只返回一个图片URL还有一种情况验证码接口返回的是JSON但里面只有一张图片的URL{ captchaId: xxx, imgUrl: /captcha/image/xxx.png }这种就更绕了一层。你需要先用JSON Extractor取出imgUrl再发一次HTTP请求去拉取图片然后在这次请求的PostProcessor里把图片字节保存下来。相当于把场景一和场景二串起来。实际配置就是两个HTTP请求第一个请求验证码信息拿URL第二个请求${imgUrl}拿图片。注意第二个请求的响应会直接被Jmeter存到内存里如果图片比较大建议在HTTP请求的“响应数据”设置里选择“保存响应到文件”但为了脚本可控我还是建议用PostProcessor自己处理字节。3.4 场景四验证码图片需要Header或Cookie才能拿到不少系统的验证码接口需要带特定的Header比如X-Device-Id或者依赖登录前的临时Cookie才能返回正确图片。这种场景容易被忽视结果就是你拿到的是一张“陌生人”的验证码怎么识别都登录不进去。解决办法是确保获取验证码图片的请求和最终提交登录的请求在同一个线程组、同一个Cookie管理器下。Jmeter里的HTTP Cookie Manager默认是自动保存服务端Set-Cookie的所以只要“获取验证码”和“提交登录”两个请求处于同一线程组并且中间没有清理Cookie一般问题不大。如果服务端返回的验证码ID需要你手动拼到登录请求里那就把captchaId用正则提取器或JSON提取器取出来作为登录请求的参数一起提交。我在实际项目里遇到过一个系统服务端不靠Cookie而是靠captchaId参数找回验证码答案这种时候Cookie反而没那么重要captchaId才是关键。4. OCR识别在Jmeter里的完整实现图片拿到手了接下来就是识别。这一部分我给出两种可落地的方案方案一是直接在Jmeter里调用Java OCR库适合快速验证方案二是把OCR独立成一个服务适合工程化复用。4.1 为什么用JSR223而不是BeanShell先说一个很多人问的问题为什么要用JSR223 SamplerBeanShell也行啊从功能上讲BeanShell也能写脚本但JSR223有几个明显优势性能更好JSR223的Groovy引擎在Jmeter中只初始化一次后续脚本执行走编译缓存而BeanShell是边解释边执行在高并发下性能差距明显。依赖管理方便Groovy可以方便地引入外部Java类库比如Tess4J、OpenCV的Java接口BeanShell也不是不行但踩坑更多。语法更舒服Groovy对Java的兼容度极高你写Java代码它基本能跑而且声明变量、字符串处理更灵活。所以我的建议统一用JSR223 Sampler Groovy别再用BeanShell写新脚本了。4.2 方案一Jmeter里直接调用Tess4JTess4J是Tesseract OCR的Java API封装。使用前需要准备下载Tesseract安装包Windows直接去GitHub下exe安装即可安装时必须选上中文简体语言包虽然识别数字和字母用英文语言包就够了但以防万一。下载tess4j的jar包放到Jmeter的lib/ext目录或者通过lib目录引入。记住Tesseract的tessdata路径这是语言包所在的目录。下面是一段完整的JSR223 Sampler脚本import net.sourceforge.tess4j.Tesseract import javax.imageio.ImageIO import java.awt.image.BufferedImage import java.awt.Graphics2D import java.awt.Color // 读取前面保存的验证码图片 String filePath vars.get(captchaFilePath) BufferedImage originalImage ImageIO.read(new File(filePath)) // 1. 图片缩放放大2倍很多验证码小字识别率会骤降 int newWidth originalImage.getWidth() * 2 int newHeight originalImage.getHeight() * 2 BufferedImage scaledImage new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_RGB) Graphics2D g2d scaledImage.createGraphics() g2d.drawImage(originalImage, 0, 0, newWidth, newHeight, null) g2d.dispose() // 2. 灰度化 BufferedImage grayImage new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_BYTE_GRAY) Graphics2D g2dGray grayImage.createGraphics() g2dGray.drawImage(scaledImage, 0, 0, null) g2dGray.dispose() // 3. 用临时文件保存处理后的图片 File tempFile File.createTempFile(captcha_ocr, .png) ImageIO.write(grayImage, png, tempFile) // 4. 初始化Tesseract并识别 Tesseract tesseract new Tesseract() tesseract.setDatapath(D:/Tesseract-OCR/tessdata) tesseract.setLanguage(eng) // 关键限定字符集数字验证码就只允许数字识别率立刻提升 tesseract.setTessVariable(tessedit_char_whitelist, 0123456789) // 关键告诉引擎图片是一行文字不要做多行排版分析 tesseract.setTessVariable(tessedit_pageseg_mode, 7) String result tesseract.doOCR(tempFile) // 清理非数字字符 result result.replaceAll([^0-9], ) log.info(OCR识别结果: result) vars.put(captchaCode, result) // 清理临时文件 tempFile.delete()这段脚本最重要的两个参数tessedit_char_whitelist字符白名单。如果你的验证码只有数字就写成0123456789如果是数字小写字母就写0123456789abcdefghijklmnopqrstuvwxyz。白名单缩小了识别范围Tesseract的准确率会有质的提升。tessedit_pageseg_mode页面分割模式。设成7表示把图片当成一行文本设成8是单字识别。对于4-6位验证码7通常最合适。如果字符间距特别大可以试8。需要注意的是Tesseract对图片尺寸非常敏感。有些验证码图片宽度只有80像素直接识别率惨不忍睹放大2倍后再识别效果立竿见影。但别放大太多3倍以上字符容易变形反而降低准确率。4.3 方案二把OCR独立成服务工程化推荐方案一适合在单机上快速验证脚本但我越往后越发现把OCR逻辑全塞在Jmeter里并不是一个好做法Jmeter脚本越来越臃肿灰度化、二值化、去噪、识别、重试……全挤在一个JSR223里脚本可读性极差。团队协作困难开发同学想优化识别逻辑还要去你的Jmeter脚本里改Groovy开发体验很差。并发压力Jmeter分布式压测时每个Agent都会跑OCR本机性能和资源消耗不可控。所以后来我把OCR识别逻辑抽出来单独搭了一个Python Flask服务专门负责“接收图片返回识别结果”。Jmeter只需要发一个HTTP请求把base64或图片文件传过去拿回识别结果就行。这个服务的核心代码大概长这样from flask import Flask, request, jsonify import base64 import re import pytesseract from PIL import Image, ImageFilter, ImageEnhance import io app Flask(__name__) app.route(/ocr/captcha, methods[POST]) def ocr_captcha(): data request.get_json() if not data or image not in data: return jsonify({code: 400, msg: missing image}), 400 # 接收base64图片 base64_str data[image] if , in base64_str: base64_str base64_str.split(,)[1] img_bytes base64.b64decode(base64_str) img Image.open(io.BytesIO(img_bytes)) # 预处理转灰度、放大、增强对比度 img img.convert(L) img img.resize((img.width * 2, img.height * 2), Image.LANCZOS) img img.filter(ImageFilter.MedianFilter(size3)) img ImageEnhance.Contrast(img).enhance(2.0) # 指定Tesseract路径字符白名单 pytesseract.pytesseract.tesseract_cmd rD:/Tesseract-OCR/tesseract.exe custom_config r--psm 7 -c tessedit_char_whitelist0123456789 text pytesseract.image_to_string(img, configcustom_config) code re.sub(r[^0-9], , text) return jsonify({code: 0, result: code}) if __name__ __main__: app.run(host0.0.0.0, port8899, threadedTrue)Jmeter那边就非常简单了一个HTTP请求POST到http://localhost:8899/ocr/captchaBody里带上JSON格式的{image: ${captchaBase64}}然后用JSON Extractor取result字段存入captchaCode变量。把OCR服务独立出去的好处是显而易见的Jmeter脚本回归“发请求、断言、取参数”的本职工作OCR服务的识别逻辑可以持续优化不影响测试脚本多个测试项目可以共用同一个OCR服务。4.4 识别结果回填与登录提交识别出验证码之后最后一步就是把结果填到登录请求里。登录请求的参数一般是参数名取值username${username}password${password}captcha${captchaCode}captchaId${captchaId}提交之后记得加一个断言来验证登录是否成功。常见断言方式是一个响应断言检查返回的JSON里是否包含token或登录成功关键字。如果返回的是{code:401,message:未登录,请登录!}或者提示验证码错误说明识别结果不对需要在日志里把识别出的captchaCode和原始验证码图片路径打出来方便复查原因。我在实际项目里有一个经验不管OCR识别率多高都要在提交登录后做一次结果判断识别错了就重试整个流程。这就是下一章要讲的识别率优化和重试机制。5. 识别率实战优化与重试机制OCR识别率不可能做到100%哪怕你接打码平台也一样。对付不稳定不能靠赌运气要靠优化和重试两手抓。5.1 影响识别率的三座大山先明确敌人是谁。我总结下来验证码图片里影响Tesseract识别率的因素主要是背景噪声颗粒噪点、干扰线、花纹背景。这些都会被OCR当成字符的一部分导致误识别。字符扭曲和粘连字符之间的间隔不规律甚至互相重叠Tesseract很难正确切分字符。颜色干扰彩色字符在灰度化之后对比度变差边界模糊。很多验证码故意用相近色制造干扰。知道敌人之后预处理就有的放矢了。5.2 OpenCV预处理的关键调参如果你用的是Python OCR服务可以很方便地加入OpenCV预处理。基础的流程是灰度化把三通道彩色图转成单通道灰度图减少颜色干扰二值化把灰度图变成黑白图让字符和背景彻底分开去噪用中值滤波或高斯滤波去除颗粒噪声膨胀/腐蚀让断裂的字符笔画重新连起来或者去掉孤立噪点。其中二值化是最核心的一步。OpenCV里的cv2.threshold函数有几种阈值方式我推荐用OTSU自适应阈值它可以根据图片的灰度分布自动算出最优阈值不需要你手工指定。import cv2 import numpy as np # 读图 img cv2.imread(captcha.png, cv2.IMREAD_GRAYSCALE) # OTSU二值化自动计算阈值 _, binary cv2.threshold(img, 0, 255, cv2.THRESH_BINARY | cv2.THRESH_OTSU) # 中值滤波去掉噪点 binary cv2.medianBlur(binary, 3) # 膨胀一次把细的笔画连起来 kernel np.ones((2, 2), np.uint8) binary cv2.dilate(binary, kernel, iterations1) cv2.imwrite(captcha_processed.png, binary)这里有一个经验参数如果识别结果经常是“少了字符”说明膨胀不够字符笔画断了如果识别结果多出一些奇怪的字符说明滤波不够噪点没去干净。你不需要理解每个函数的数学原理只需要知道“断笔画就膨胀噪点就滤波”这个直观的因果关系然后多试几次。5.3 Tesseract的引擎参数调整预处理之外Tesseract自己的参数也值得调。除了前面讲过的tessedit_char_whitelist和--psm 7还有两个常用技巧调整preserve_interword_spaces如果你的验证码字符间距比较大适当保留空格让Tesseract觉得这是多个独立的词而不是一个字识别会准一些指定白色字符黑色背景或反色Tesseract对白底黑字的识别率通常高于黑底白字。如果验证码图片是黑底白字可以用cv2.bitwise_not反色处理后再识别二值化后做一次缩放有时识别率不高不是字看不清而是图片尺寸太小。二值化之后再放大2倍效果往往比先放大再二值化更好。还有一点要特别说同一个验证码系统它的图片生成算法是相对固定的。你拿到一批历史验证码图片离线测试识别率发现某个预处理参数效果特别好那就在这个系统上固定下来。每个系统的调参结果是不能通用的别指望一套参数打天下。5.4 重试机制把识别率从80%变成95%调参不能解决所有问题但重试机制可以。思路很简单识别失败就重新获取验证码图片再识别再提交。最多重试3次。Jmeter里的实现用Loop Controller If Controller组合。在测试计划中加一个Loop Controller循环次数设为3循环里依次是获取验证码接口 → 保存图片 → OCR识别 → 提交登录提交登录后加一个JSR223 PostProcessor判断登录响应是否包含token或登录成功标志如果成功了用vars.put(loginSucceeded, true)打标记在Loop Controller的下一层加If Controller条件为${loginSucceeded} ! true控制是否继续循环。核心是每次循环都会重新获取验证码图片所以每次识别结果都不同。哪怕单次识别率只有80%循环3次后成功率就是1-(1-0.8)^3约等于99.2%。重试机制比疯狂调参实在多了。为什么这个方案能解决绝大多数“验证码识别不准”的问题因为它把不确定性转移到了概率层面。你不需要追求99%的单次识别率只要保证30%以上重试3次就能达到可接受的回归稳定性。这也是我强烈推荐所有做接口自动化的人先搞定重试的原因。5.5 日志与现场保留最后一条经验识别失败时一定要把原始图片和识别结果记录下来。我习惯在OCR脚本里做两件事把识别成功的图片重命名为success_${验证码}_${时间戳}.png把识别失败的图片重命名为fail_${识别结果}_${时间戳}.png单独放到fail目录。不要小看这一步。当你积累了一批失败样本再去看这些图片你会发现它们有共同的视觉特征噪声重、字符歪、有干扰线。你甚至可以拿着这些样本去离线跑不同的预处理参数针对性地调优。没有样本调参就是闭着眼睛瞎试。Jmeter里实现也很简单在保存图片脚本里根据后续识别结果重命名文件即可。如果你用的是独立的OCR服务可以在服务端直接做这个保存动作。6. 压测和并发场景下怎么处理验证码到这里接口回归里的图片验证码基本能稳定跑通了。但对做Jmeter的人来说还有一个绕不开的场景压测。并发一上来验证码问题会出现新的变化。6.1 并发压测时不要对每个线程都OCR如果500个并发用户同时跑登录每个线程都去OCR第一波识别请求就把OCR服务打爆了或者Jmeter本机CPU瞬间飙到100%压测结果也失真了。所以压测场景必须换策略尽量走后门压测前和开发确认测试环境是否提供万能验证码或者是否可以直接跳过验证码校验。如果必须走OCR用少量并发只跑一次登录把登录成功后的Token/Session提取出来后续业务压测都用这个Token而不是每个线程都走“验证码登录”流程。登录接口本身要压测时把OCR请求数控制住比如用Synchronizing Timer或Constant Throughput Timer限制OCR请求频率或者做成固定几个预取验证码然后并发复用。记住一句话压测的目的不是测验证码识别而是测业务接口的承压能力。验证码识别只是登录流程上的一颗“螺丝钉”不能让它成为压测的瓶颈。6.2 登录Token复用与Cookie管理压测时通常的做法是先通过一次或少数的“验证码登录”请求拿到登录后的Cookie或Token然后用CSV Data Set Config或props把Token传给其他线程组。这里有一个Jmeter细节不同线程组之间的变量是不共享的跨线程组传递Token要用${__setProperty(loginToken, ${token})}写入属性然后在另一个线程组用${__property(loginToken)}读取。Cookie管理也一样。如果你需要在压测时保持会话HTTP Cookie Manager的“每次迭代清除Cookie”选项记得关掉否则每个线程每跑一次迭代就丢一次登录状态压测全变成登录压测了。6.3 让OCR服务具备扛压能力如果真的遇到了“登录接口必须压测且验证码不能关”的场景而你把OCR服务独立出去了那OCR服务本身也要做一定的优化给OCR服务加一个连接池或队列请求并发太高时排队处理而不是直接超时Jmeter端设置合理的超时时间OCR慢的话先评估是否影响整体压测给OCR服务加上缓存策略——同一个验证码ID的识别结果如果已经被成功提交就不用重复识别。其实这个场景我在实际项目里只遇到过两次。绝大多数时候开发在压测前都会配合把验证码关掉或者提供万能码。如果你遇到那种“压测环境里验证码必须保留”的奇葩要求那尽量争取把这个要求从“保留校验”改成“保留流程”也就是服务端依然下发验证码图片、依然走校验逻辑但配置一个固定值能通过。这样既保留了验证码链路的完整性又不会让压测被OCR拖死。6.4 关于分布式压测的一个提醒Jmeter做分布式压测时Master和Agent各自独立运行如果你在脚本里调用了本地的OCR服务所有Agent默认都会访问同一台Master机器上的服务。这需要你在防火墙层面保证Agent能访问到OCR服务的端口否则压测到一半全部登录请求会因为OCR服务不可达而失败。另外一个更隐蔽的坑是Tesseract的tessdata路径在每台Agent机器上要存在。如果你的脚本用的是方案一JSR223直接调Tess4JAgent机器上必须安装Tesseract而且路径一致。这就是我推荐独立OCR服务的另一个原因——识别逻辑只部署在一台机器上Agent端永远是干净的。7. 最后再分享几个实战细节文章写到这主流程已经完整了。最后补几条我在实际项目里反复用到、但网上很少有人提的细节。7.1 验证码图片接口本身也要做断言很多人只给登录请求加了断言忽略了验证码接口本身。我建议给验证码接口也加一个简单的响应断言比如检查状态码是200、响应字节数大于某个阈值。为什么因为验证码图片接口偶尔会返回占位图或空图如果图片本身就是错误的后面再怎么识别都没意义。尽早发现尽早重新请求比等登录失败再重试效率高得多。7.2 识别结果里混进中文字符的情况Tesseract的eng语言包偶尔会把数字1识别成字母l把0识别成O。如果你设了白名单这种混淆会少很多但只要服务端验证码采用了平滑字体OCR还是可能把1识成l。这时候要么把验证码系统历史上出现过的高频混淆字符对记录下来做一层字符映射替换要么在重试机制里直接把“识别结果长度不等于验证码位数”的请求判定为失败直接重试。识别结果长度不对大多数情况下就是识别错了不值得为了抢救这一次结果做更多优化。7.3 把“验证码处理”拆成一个公共能力最后一条也是我吃过亏之后得出的经验不要把验证码处理逻辑散落在某一个Jmeter脚本里。你的团队可能有登录、注册、找回密码等多个接口都涉及验证码每处都复制一份OCR脚本后期维护会让人崩溃。更好的做法是像第4部分说的那样把它做成一个独立的OCR识别服务Jmeter、Postman、Apifox都可以通过HTTP调用。这样不管是测试环境登录还是日常调试走的都是同一套识别逻辑优化一次处处生效。如果你们团队连独立服务都觉得重那至少也应该把“获取验证码→保存图片→调用OCR→回填”这个流程封装成一个Jmeter片段Test Fragment通过Include Controller复用。这样至少不用在多个脚本里维护同一份Groovy。我在实际使用中最深的一个体会是验证码处理的终极方案不是把识别率做到99%而是让验证码不再成为自动化的瓶颈。后门能做到这一点就用后门后门拿不到用OCR加重试也能做到业务可接受的稳定真的遇到滑块、点选这种复杂验证码承认它不适合自动化手动处理或者打码平台兜底也是一种成熟的选择。做接口测试目标是稳定、高效地发现业务问题别跟一个验证码死磕到底。
返回列表