在作自動化測試或壓力測試時,驗證碼老是一個問題。在以往的壓力測試經歷中,測試通常在獨立的測試環境中進行,能夠放心禁用驗證碼或使用萬能驗證碼,這個是最實用的。可是,這兩天我嘗試了一個使用第三方的圖形圖像識別工具來完成驗證碼識別並經過Jmeter完成登陸的過程,識別工具的識別成功率有限,所以本篇估計僅能在理論範圍內適用。html
本篇內容大部份內容來自於該做者的文章:http://blog.csdn.net/xreztento/article/details/48682923java
整體目的:給Jmeter寫一個後置處理器,用來將上一個請求響應返回的驗證碼圖片識別成文字,並將識別內容保存爲Jmeter的一個參數,這個參數供登陸post請求進行登陸驗證,從而完成登陸的自動化過程。ios
工具apache
(1)第三方圖形圖像識別工具:tesseract-ocr 下載地址:http://code.google.com/p/tesseract-ocr/downloads/list 基本沒法下載,已上傳到個人百度網盤app
安裝後,能夠在cmd下試一試是否安裝成功:編輯器
在cmd下輸入命令:tesseract d:\123.jpg result -l eng 意思是將D盤下的123.jpg 識別後放在result.txt下ide
(2)須要用到的jar包:工具
Jmeter插件開發相關的jar包: ApacheJmeter_core.jar jorphon.jar logkit-2.0.jar 這些在Jmeter的lib中都有 直接導入工程項目便可post
圖形處理相關的jar包:jai-imageio-1.1.jar swingx-1.6.1.jar 從網上下的,已上傳到百度雲盤 jar 文件夾下學習
插件開發
用java IDE新建一個工程項目,實現兩個部分,一個是識別圖片,一個是Jmeter插件的UI部分。工程項目完成目錄爲:
package com.test.huu; import java.awt.Color; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.Locale; import javax.imageio.IIOImage; import javax.imageio.ImageIO; import javax.imageio.ImageReader; import javax.imageio.ImageWriteParam; import javax.imageio.ImageWriter; import javax.imageio.metadata.IIOMetadata; import javax.imageio.stream.ImageInputStream; import javax.imageio.stream.ImageOutputStream; import com.sun.media.imageio.plugins.tiff.TIFFImageWriteParam; public class ImageIOHelper{ 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 block e.printStackTrace(); } finally { if(iis != null){ try { iis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(ios != null){ try { ios.close(); } catch (IOException e) { // TODO Auto-generated catch block e.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")); } //給圖片降噪 提升識別度 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; } }
package com.test.huu; 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 eng wait = 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 block e.printStackTrace(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(in != null){ try { in.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } tempImage.delete(); return sb.toString(); } }
package com.test.huu; import java.io.File; public class TestOCR { public static void main(String[] args) { String path = "D://124.jpg"; System.out.println("ORC Test Begin......"); try { String valCode = new OCR().recognizeText(new File(path), "jpeg"); System.out.println(valCode); } catch (Exception e) { e.printStackTrace(); } System.out.println("ORC Test End......"); } }
package com.test.huu; 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 long serialVersionUID = 1L; /** * */ private static final Logger log = LoggingManager.getLoggerForClass(); @Override public void process() { // TODO Auto-generated method stub JMeterContext 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"; String path = "D://" + id + ".jpg"; if(status.equals("200")){ byte[] buffer = previousResult.getResponseData(); FileOutputStream out = null; File file = null; try { file = new File(path); out = new FileOutputStream(file); out.write(buffer); out.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { if(out != null){ try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } try { String vcode = new OCR().recognizeText(file, "jpeg"); vcode = vcode.replace(" ", "").trim(); JMeterVariables var = context.getVariables(); var.put("vcode", vcode); // var.put("vuser", String.valueOf(id)); } catch (Exception e) { e.printStackTrace(); } } } }
package com.test.huu; import org.apache.jmeter.processor.gui.AbstractPostProcessorGui; import org.apache.jmeter.testelement.TestElement; public class VcodeExtractorGUI extends AbstractPostProcessorGui{ /** * */ private static final long serialVersionUID = 1L; /** * */ @Override public TestElement createTestElement() { // TODO Auto-generated method stub VcodeExtractor extractor = new VcodeExtractor(); modifyTestElement(extractor); return extractor; } @Override public String getLabelResource() { // TODO Auto-generated method stub return this.getClass().getName(); } @Override public String getStaticLabel() {//設置顯示名稱 // TODO Auto-generated method stub return "VcodeExtractor"; } @Override public void modifyTestElement(TestElement extractor) { // TODO Auto-generated method stub super.configureTestElement(extractor); } }
插件生成
插件開發完成後,在Eclipse中 export-Runnable jar file ,將必要的依賴庫加進去,最後會生成一個 .jar 文件
注意:圖形相關的jar包 直接使用時會報錯(Jmeter會報一個錯:java.lang.IllegalArgumentException: vendorName == null) 最終在網上找到了解決方案
生成jar包後,用解壓工具打開,將 /META-INF 目錄下的 MANIFEST.MF 文件用編輯器(我用的是sublime)打開,拷貝進去下面一段代碼,保存壓縮包:
Specification-Title: Java Advanced Imaging Image I/O Tools
Specification-Version: 1.1
Specification-Vendor: Sun Microsystems, Inc.
Implementation-Title: com.sun.media.imageio
Implementation-Version: 1.1
Implementation-Vendor: Sun Microsystems, Inc.
插件插入Jmeter
將 .jar 文件放入Jmeter 安裝路徑下 lib/ext/ 目錄下,重啓Jmeter
能夠看到,咱們新開發的後置處理器 VcodeExtractor
再看下大體的登陸過程測試計劃:
登陸的post請求參數中,可使用Vcode,Vcode是咱們開發的後置處理器 VcodeExtrator 返回的從圖片驗證碼中識別出來的字符串
插件效果驗證
把測試計劃跑一次 根據察看結果樹 看下效果
登陸請求成功啦,可是圖片識別也不是百分百成功,部分失敗狀況下,登陸請求確定會失敗。Tesseract-OCR也有訓練識別的功能,可是再也不繼續研究了。
關於Tesseract-OCR的延展性學習可參考:
(1)http://www.cnblogs.com/alex-blog/archive/2012/10/08/2714984.html
(2)http://blog.csdn.net/ycb1689/article/details/8520954
(3)http://www.52itstyle.com/thread-4803-1-1.html