我们在性能测试中总会时不时地遭遇到来自于应用系统的各种阻碍,图片验证码就是一类最常见的束缚,登录或交易时需要按照图片中的内容输入正确的验证信息后,数据才可以提交成功,这使得许多性能测试工具只能望而却步。网上也出现了一些LoadRunner的解决方案,但结合LoadRunner对于C脚本内存控制和识别成功率低下等诸多问题,这些方案没有什么实际用途。然而,为JMeter开发插件却给我们提供了一条可行的道路来冲破图片验证码的束缚!

选择一个理想的第三方图形图像识别工具

在此我们首先需要一个比较理想的图形图像识别工具来完成将验证码中的图形图像文字识别转换为文本文字主体识别工作,在此我们选择Tesseract, Tesseract是一个开源的OCR(Optical Character Recognition,光学字符识别)引擎,可以识别多种格式的图像文件并将其转换成文本,发布在Googel Project上,地址为http://code.google.com/p/tesseract-ocr/(但Googel Project停止维护后不知道现在在哪里维护)。

一组用于验证码识别的JMeter插件

我们常见的验证码图片样本如下:

1. 降噪
当你遇到这样的验证码时,首先你要做的就是降噪,将背景的一些干扰我们识别文本内容的线条过滤掉,人眼需要降噪,识别软件在进行识别前也需要帮助其进行降噪来加大识别成功率,通常降噪的方案是对图片像素点进行逐个扫描,通过创建降噪规则对背景噪音进行过滤,如上面的样本,我们可以建立如下降噪规则和方法:

public static int isFilter(int colorInt) {  Color color = new Color(colorInt);if ((color.getRed() > 85 && color.getRed() < 255) && (color.getGreen() > 85 && color.getGreen() < 255) && (color.getBlue() > 85 && color.getBlue() < 255)) {  return 1;  }  return 0;}  public static BufferedImage removeBackgroud(BufferedImage img)  throws Exception {  int width = img.getWidth();  int height = img.getHeight();  for (int x = 0; x < width; ++x) {  for (int y = 0; y < height; ++y) {  if (isFilter(img.getRGB(x, y)) == 1) {  img.setRGB(x, y, Color.WHITE.getRGB());  }}  }  return img;}

将图片文件转换为BufferedImage对象进行去背景降噪,通过调用removeBackgroud方法,我们降噪后的图片如下:

可以看到效果非常明显,但降噪也有它的局限性,比如会把一些需要正常显示的图形文字过滤掉一部分,诸如此类问题我们会在后面的介绍中通过其他方式解决,但对于图形图像识别软件的输入来说,必须对其加以降噪才能保证读取正确率。

2. 识别插件(第一个Extractor插件)
我们在最初的章节介绍了Extractor的基本实现方法,在此我们还是简单回顾一下后置处理器的一些功能,下图显示了JMeter为我们默认提供的后置处理器:

所谓后置处理器是相对Sampler的后置,主要用于处理Sampler所抽样得到的SamplerResult对象,对SamplerResult做修饰或通过SamplerResult抽取信息,最常使用的是“正则表达式提取器”、“CSS/JQuery Extractor”、“XPath Extractor”,使用它们可以实现性能测试脚本中最重要的“关联”操作。

好了,我们的需求是对验证码进行读取,即通过验证码URL获取到图片资源(这部分由“HTTP请求Sampler”完成),然后提取资源中的图形图像信息作为Tesseract的输入,最后在将Tesseract的输出作为一个JMeter参数数据进行保存。惯例使用分离法,分为逻辑控制部分VcodeExtractor和GUI部分VcodeExtractorGUI,另外,还包括对图片进行处理的ImageIOHelper类以及实现调用Tesseract对验证码信息识别并读取的OCR类。

ImageIOHelper主要包含两大部分,一部分就是前面所介绍的降噪逻辑,另一部分是将图片格式转换为tiff格式以更好地进行识别,这部分的代码参考如下:

public static File createImage(File imageFile, String imageFormat) {File tempFile = null;ImageInputStream iis = null;ImageOutputStream ios = null;ImageReader reader = null;ImageWriter writer = null;try {Iterator<ImageReader> readers = ImageIO.getImageReadersByFormatName(imageFormat);reader = readers.next();iis = ImageIO.createImageInputStream(imageFile);reader.setInput(iis);IIOMetadata streamMetadata = reader.getStreamMetadata();TIFFImageWriteParam tiffWriteParam = new TIFFImageWriteParam(Locale.CHINESE);tiffWriteParam.setCompressionMode(ImageWriteParam.MODE_DISABLED);Iterator<ImageWriter> writers = ImageIO.getImageWritersByFormatName("tiff");writer = writers.next();BufferedImage bi = removeBackgroud(reader.read(0));IIOImage image = new IIOImage(bi,null,reader.getImageMetadata(0));tempFile = tempImageFile(imageFile);ios = ImageIO.createImageOutputStream(tempFile);writer.setOutput(ios);writer.write(streamMetadata, image, tiffWriteParam);} catch (IOException e) {e.printStackTrace();} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if(iis != null){try {iis.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(ios != null){try {ios.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}if(writer != null){writer.dispose();}if(reader != null){reader.dispose();}}return tempFile;}private static File tempImageFile(File imageFile) {String path = imageFile.getPath();StringBuffer strB = new StringBuffer(path);return new File(strB.toString().replaceFirst("jpg", "tif"));}

OCR类主要是通过Process调用已经安装的Tesseract程序,调用命令基本形式为tesseract xxx.tif 1 -l eng,参考如下代码:

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;public class OCR {private final String LANG_OPTION = "-l";private final String EOL = System.getProperty("line.separator");private String tessPath = "D://Program Files (x86)//Tesseract-OCR";public String recognizeText(File imageFile,String imageFormat) {File tempImage = ImageIOHelper.createImage(imageFile,imageFormat);File outputFile = new File(imageFile.getParentFile(),"output" + imageFile.getName());StringBuffer sb = new StringBuffer();List<String> cmd = new ArrayList<String>();cmd.add(tessPath+"//tesseract");cmd.add("");cmd.add(outputFile.getName());cmd.add(LANG_OPTION);cmd.add("eng");     ProcessBuilder pb = new ProcessBuilder();pb.directory(imageFile.getParentFile());cmd.set(1, tempImage.getName());pb.command(cmd);pb.redirectErrorStream(true);Process process = null;BufferedReader in = null;int wait;try {process = pb.start();//tesseract.exe xxx.tif 1 -l engwait = process.waitFor();if(wait == 0){in = new BufferedReader(new InputStreamReader(new FileInputStream(outputFile.getAbsolutePath()+".txt"),"UTF-8"));String str;while((str = in.readLine())!=null){sb.append(str).append(EOL);}in.close();}else{tempImage.delete();}new File(outputFile.getAbsolutePath()+".txt").delete();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if(in != null){try {in.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}tempImage.delete();return sb.toString();}
}

VcodeExtractor类继承AbstractScopedTestElement抽象类,实现PostProcessor接口的process方法,来处理利用OCR读取验证码信息的逻辑控制,参考代码如下:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;import org.apache.jmeter.processor.PostProcessor;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.AbstractScopedTestElement;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;public class VcodeExtractor extends AbstractScopedTestElement implements PostProcessor, Serializable{private static final Logger log = LoggingManager.getLoggerForClass();@Overridepublic void process() {// TODO Auto-generated method stubJMeterContext context = getThreadContext();SampleResult previousResult = context.getPreviousResult();if (previousResult == null) {return;}log.debug("VcodeExtractor processing result");String status = previousResult.getResponseCode();int id = context.getThreadNum();String imageName = id + ".jpg";if(status.equals("200")){byte[] buffer = previousResult.getResponseData();FileOutputStream out = null;File file = null;try {file = new File(imageName);out = new FileOutputStream(file);out.write(buffer);out.flush();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if(out != null){try {out.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}try {   String vcode = new OCR().recognizeText(file, "jpg"); vcode = vcode.replace(" ", "").trim();JMeterVariables var = context.getVariables();var.put("vcode", vcode);var.put("vuser", String.valueOf(id));} catch (Exception e) {e.printStackTrace();}    }}}

代码逻辑非常简洁,即通过getThreadContext()方法获取当前线程(vuser)的上下文,从而从上下文中获取到前一个Sampler所抽样的结果,为保证结果不为空我们做了一个简单的处理,也可以添加一些更为精细的控制,如下代码:

if(context.getPreviousSampler() instanceof HTTPSampler){return;
}

判断前一个Sampler是否为HTTPSampler,以限定有效使用范围。
将previousResult.getResponseData()保存为文件后,通过前面我们创建的OCR完成识别任务后,将识别结果通过JMeterVariables对象保存下来,在此我们分别建立了两个参数”vcode”和”vuser”,后面我们可以用它们进行测试。

该版本的VcodeExtractorGUI类只是单纯实现一个可视化的界面用于在测试计划Tree中进行操作:

import org.apache.jmeter.processor.gui.AbstractPostProcessorGui;
import org.apache.jmeter.testelement.TestElement;public class VcodeExtractorGUI extends AbstractPostProcessorGui{@Overridepublic TestElement createTestElement() {// TODO Auto-generated method stubVcodeExtractor extractor = new VcodeExtractor();modifyTestElement(extractor);return extractor;}@Overridepublic String getLabelResource() {// TODO Auto-generated method stubreturn this.getClass().getName();}@Overridepublic String getStaticLabel() {//设置显示名称// TODO Auto-generated method stubreturn "VcodeExtractor";}@Overridepublic void modifyTestElement(TestElement extractor) {// TODO Auto-generated method stubsuper.configureTestElement(extractor);}
}

将插件打包插入JMeter框架,可以在后置处理器列表中查看到VcodeExtractor组件:

建立测试计划,看看我们的插件对图片验证码的识别情况如何:

测试中,我们设置了5个线程(vuser),可以看到已经成功识别验证码数字,并把他们保存到了对应的vcode参数,但这样就够了吗?显然还差得远,有时你会发现识别结果是这样的:

这意味着识别存在错误!

3. 提高识别成功率
识别成功率是成败的关键,提升成功率可以采取以下方案:

  • 训练Tesseract

    提供大量样本来训练Tesseract对特定图形的识别成功率。

  • 修正错误的识别结果

    有些识别错误是这样的,如:
    将J识别为[,将M识别为|\/|,将N识别为||,这种识别错误是机器识别离散一些的像素点产生的,人眼是可以修正的,因此,我们可以建立映射表方式将错误字符进行修正。

  • 避免混淆形状接近的图形字符

    有些识别错误是这样的,如:
    将5识别为S,将1识别为I,将0识别为O,这种识别错误是纯的图形混淆产生的,人眼也可能犯此类错误,我们管它叫“看不清”。

4. 看不清,换一张(第一个Controller插件)
“看不清,换一张”无论对人眼或机器识别都是一种弥补方案,我们对于“看不清”的字符需要模拟换一张重新识别的操作,这里我们引入一个新的插件Controller(逻辑控制器),照例我们先来回顾一下该插件的一些功能,下图显示了JMeter为我们默认提供的逻辑控制器:

前面的章节曾经介绍过所谓逻辑控制器主要就是用来控制线程行为的,当然也包括一些用于划分Sampler或功能边界的控制器如事务控制器和录制控制器,主要是依靠一些限定的条件或阈值的判断,按想要的方式控制总体线程或单独线程行为。

好了,我们的需求很明确“看不清,换一张”,在此可以完全照搬循环控制器的源代码,参考LoopController类和LoopControlPanel类,只需要对LoopController在每次循环结束后判断是否退出的函数中增加我们对于图片是否看清的逻辑,代码如下:

private final static String PATTERN = "34789ABCEFHKLPRTUVWXY"private boolean isVerify(String vcode){int length = vcode.length();//对长度进行判断if(length != 4){return false;}//对内容进行判断for(int i = 0; i < length; i++){if(PATTERN.indexOf(vcode.toCharArray()[i]) < 0){return false;}}return true;}    @Overridepublic Sampler next() {JMeterContext context = getThreadContext();JMeterVariables var = context.getVariables();String vcode = var.get("vcode");if(vcode != null){if(isVerify(vcode)){setDone(true);return null;}}if(endOfLoop()) {if (!getContinueForever()) {setDone(true);}return null;}return super.next();}

如果通过isVerify函数校验(看得清楚)就直接退出循环,否则(看不清楚)就接着重新请求图片验证码进行校验(换一张),创建此逻辑控制器VcodeVerifyController。

将插件打包插入JMeter框架,可以在逻辑控制器列表中查看到VcodeVerifyController组件:

一个完整的登录测试计划

这是一个标准的登录测试计划,所有要素一应俱全,HTTP Cookie管理器负责保持会话状态,CSS/JQuery Extractor负责关联,将lt的值保存为LT参数,其设置参考下图:

我们的插件组VcodeVerifyController和VcodeExtractor负责将图形验证码正确识别和读取,其中VcodeVerifyController支持尝试读取循环次数的设置:

一个登录的HTTPSampler,将读取的${vcode}参数和关联的${LT}参数一并POST到Web服务器:

一个由响应断言完成的文本检查点,验证登录成功后的欢迎页面:

在此,我们设置5个线程(vuser)进行测试,测试结果如下:

全部通过了登录验证,但根据测试发现识别率成功基本在75%左右,因此,还需要进一步完善,第一是通过改进识别逻辑,第二是增加一个验证码如果识别错误重新进行识别提交登录事务的过程控制。

如何为Apache JMeter开发插件(三)——冲破图片验证码的束缚相关推荐

  1. 如何为Apache JMeter开发插件(一)

    本文转载于http://blog.csdn.net/column/details/12925.html,作者:xreztento 作者写的很精华,我打算在此系列操作一遍后,加多点截图,便于更多人更快上 ...

  2. 美图秀秀开发插件生成的图片都有哪些格式?

    M1完整版:jpg.png.gif: M2美化图片:jpg.png: M3拼图:jpg: M4头像:jpg.png. 如果是M1,默认生成jpg格式: 如果是M2或M4,则根据打开的图片而定,如果打开 ...

  3. java自动识别图片验证码插件_JMeter开发插件——图片验证码识别

    我们在性能测试中总会时不时地遭遇到来自于应用系统的各种阻碍,图片验证码就是一类最常见的束缚,登录或交易时需要按照图片中的内容输入正确的验证信息后,数据才可以提交成功,这使得许多性能测试工具只能望而却步 ...

  4. 使用burp插件实现图片验证码的识别

    早上看到朋友圈有人转了一个新的识别图片验证码的burp插件,项目主页https://github.com/f0ng/captcha-killer-modified 正好之前对类似插件的使用效果不理想, ...

  5. nmon结果分析工具_Jmeter测试工具实践:如何为Jmeter开发测试插件定制专属取样器?...

    JMeter原生支持多种不同的取样器(HTTP.TCP.JDBC.JMS等),每一种不同类型的取样器可以向服务器发送不同协议类型的请求.除了开箱即用的协议支持以外,JMeter还支持用户自己开发新协议 ...

  6. 【二十】Jmeter:插件二次开发—— JMeter 源码导入 eclipse

    目录 一.源码官网下载 二.lib 目录官网下载 三.源码 & lib 目录 下载 四.导入eclipse 一.源码官网下载 点击前往官网下载并解压:下载地址 二.lib 目录官网下载 点击前 ...

  7. 转载:一步一步和我学Apache JMeter

    声明:非原创,在一个技术群里分享到的文件,在这里copy一下,以便长久用. 一.       Apache JMeter介绍 1.  Apache JMeter是什么? Apache JMeter 是 ...

  8. 使用 Apache JMeter™工具进行性能测试

    使用 Apache JMeter™工具进行性能测试 一.性能测试的定义. 性能测试是通过自动化的测试工具模拟多种正常.峰值以及异常负载条件来对系统的各项性能指标进行测试.负载测试和压力测试都属于性能测 ...

  9. Apache jmeter基础一:用途、安装、运行

    一.Apache jmeter概述 1.Apache jmeter定义:Apache组织的开放源代码项目,是一个100%纯java程序,用于压力测试和性能测试. 2.下载地址:http://jmete ...

最新文章

  1. 看闯关东原来知道古代已经十六进制了
  2. linux hosts文件如何修改_如何修改hosts文件?让你简单方便快捷管理
  3. linux 天堂测试软件,[Ubuntu] HTTP Live Streaming 安装测试
  4. NYOJ 84 阶乘的0
  5. 设计模式-依赖倒置-Dependency Inversion Principle
  6. android程序root权限,android – 如何从源代码授予对特定应用程序的root访问权限而不是root权限?...
  7. Windows10+CUDA8.0+VS2015+CUDNN5下配置caffe
  8. 一个不错的linux学习资料下载的网址
  9. python窗体生成器_python 如何生成窗体
  10. c#类似跑马灯抽签小程序
  11. Post 页面数据,使用boundary来格式化
  12. 微信朋友圈装x代码_微信朋友圈超强装X,电影台词长截图,一键搞定
  13. 三种方法求解Fibonacci数列的递推公式为:Fn=Fn-1+Fn-2,其中F1=F2=1,当n比较大时,Fn也非常大,现在我们想知道,Fn除以10007的余数是多少。
  14. Censored! POJ - 1625
  15. intel UHD graphics 620/630 -win7 驱动
  16. Sklearn实现非线性回归
  17. revit出图建模【尺寸定位标注】功能
  18. 风投 红杉资本 Sequoia Capital
  19. 公信宝区块链技术和应用白皮书
  20. DelayQueue 的简单使用

热门文章

  1. 市值破7000亿美元 贝索斯成全球新首富,成就亚马逊的正是人工智能
  2. POI-word模板替换
  3. java-net-php-python-jspm光影婚纱影楼系统设计与实现系统计算机毕业设计程序
  4. 苹果连接电脑 计算机不显示硬盘,解决方法:Mac Mac计算机无法识别的硬盘解决方案...
  5. Python 实现用户名和密码登录
  6. f12获取网页文本_F12 - 开发者工具详解
  7. 基于SSD固态硬盘的数据库性能优化
  8. python编程语言创始人-程序员都秃顶?Python创始人笑了,养生还得学这门语言
  9. css基础属性(HTML的入门2)
  10. 诺基亚跟微软合作----出乎意料但在情理中