小编所在的项目一直以来存在一个效率较低的问题:按照产品流程,我们会在某一环节为用户提供合同,并结合用户的个人信息对合同进行填充,生成pdf,进行签章后提供给用户。 针对这个合同的测试,我们不仅需要结合用户个人信息,比对合同填充的正确性,同时也要保证用户每次生成合同的内容是正确且一致的。而针对合同的测试手段,最早开始是通过人工比对合同填充内容与数据库数据的方式进行的。但随着项目的发展,用户协议/合同数量增多、第三方接入新合同引入、代码优化重构合同相关用例必须要在多产品线进行回归,导致小编所在团队的工作量陡增。 虽然从流程上,在新合同引入时我们可以将合同确认的工作交给上游产品或商务同学,但人为地比对仍无法保证内容的正确性,且工作内容上也带来了较多重复。
2.1、需求分析: 找到了问题,现在我们简单分析一下需求:
2.2、设计思路: 场景一:最直接的方案是引入外部jar包,如PDFBox( https://pdfbox.apache.org/index.html)。PDFBox是Apache下的一个开源项目,我们可以通过 PDFBox读取、创建PDF文档,加密/解密PDF文档,从PDF和XFDF格式中导入或导出表单数据 等,实现代码如下:
private static String readPDF(File pdf) throws InvalidPasswordException, IOException { try (PDDocument document = PDDocument.load(pdf)) { document.getClass(); if (!document.isEncrypted()) {
PDFTextStripperByArea stripper = new PDFTextStripperByArea(); stripper.setSortByPosition(true); PDFTextStripper tStripper = new PDFTextStripper(); String pdfFileInText = tStripper.getText(document); String lines[] = pdfFileInText.split("\\r?\\n"); List<String> pdfLines = new ArrayList<>(); StringBuilder sb = new StringBuilder(); for (String line : lines) { System.out.println(line); pdfLines.add(line); sb.append(line + "\n"); } return sb.toString(); } } return null;}
问题:经测试使用,PDFBox提取出来的仅是文字流,而不是带有格式、顺序、标题的文档,经过PDFBox输出的字符串,我们仍需要全篇进行解析,处理并提取其中的关键字与填充信息,这样做很费劲而且不优雅。 另外一种实现思路是将文档转为有标记的文档,比如xml、html,这样的话在完成转化后我们就可以通过标签快速找到想要的元素并进行后续的操作。经调研,转化PDF文档的外部库很多,这里我们选择itextpdf。PDF和HTML的互相转化方法如下:
public static String generatePDFFromHTML(String filename, String outputPa th) throws ParserConfigurationException, IOException, DocumentException { Document document = new Document(); PdfWriter writer = PdfWriter.getInstance(document, new FileOutput Stream(outputPath)); document.open(); XMLWorkerHelper.getInstance().parseXHtml(writer, document, new Fi leInputStream(filename)); document.close(); return outputPath; } public static String generateHTMLFromPDF(String filename, String outp utPath) throws ParserConfigurationException, IOException { PDDocument pdf = PDDocument.load(new File(filename)); PDFDomTree parser = new PDFDomTree(); Writer output = new PrintWriter(outputPath, "utf-8"); parser.writeText(pdf, output); output.close(); if (pdf != null) { pdf.close(); } return outputPath; }
在完成了HTML的转化后,我们需要做的就是从HTML解析想要的元素了。小编以前写爬虫时最常用的Java HTML解析器就是Jsoup(http://www.open-open.com/jsoup/)。Jsoup不仅可以解析HTML文件、同时也直接通过HTTP、HTTPS去爬取网页源码进行解析,很方便,实现如下:
import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; public class JsoupTest { public static void main(String[] args) { String html = "<html><head><title>Sample Title</title></head>" + "<body>" + "<p>Sample Content</p>" + "<div id='sampleDiv'><a href='www.google.com'>Google</a>" + "<h3><a>Sample</a><h3>" +"</div>" + "<div id='imageDiv' class='header'><img name='google' src='goo gle.png' />" + "<img name='yahoo' src='yahoo.jpg' />" +"</div>" +"</body></html>"; Document document = Jsoup.parse(html); //通过标签提取文字 Element link = document.select("a").first(); System.out.println("Text: " + link.text()); // 查找.png结尾的图片的名字 Elements pngs = document.select("img[src$=.png]"); for (Element png : pngs) { System.out.println("Name: " + png.attr("name")); } // 查找H3元素之后的元素 Elements sampleLinks = document.select("h3 > a"); for (Element link : sampleLinks) { System.out.println("Text: " + link.text()); } } }
在前两步完成后,后面要做的就是从数据库拿数据进行比对,这里就不再赘述了
场景二:此场景的整体思路就是拿到此基线下的各合同PDF,然后拿新生成的合同进行比对,比对内容包括格式、文案、图片、签章坐标系等。如果复用上面的思路,那么实现原理是提取合同中的所有元素进行比较。这里存在的一个问题是一整个流程下来可能存在十数个合同,我们需要针对每个合同进行一一解析;另外此方法也无法针对位置一类的校验点进行检查。 经小编的再次调研,网上有很多的文档比对解决方案,其中applitools(https://applitools.com/)提供了CLI的解决方案,我们只需注册一个免费账号,获取到apikey,执行命令即可。
java -jar ImageTester.jar -k $APPLITOOLS_API_KEY -f <PATH>/pdf_directory/
那么问题来了,如何把此步骤加到整个自动化的流程中呢?
步骤一:移动下载的PDF至指定的目录
File downloadedPDF = new File("C:\\Users\\Tests\\Downloads\\" + contract+ ".pdf"); String destination = "C:\\Users\\resources\\contracts\\" + contract+ ".pd f"; FileUtils.moveFile(downloadedPDF, destination); public static boolean moveFile(File file, String destination){ File existingFile = new File(destination); if(existingFile.exists()){ existingFile.delete(); } return file.renameTo(new File(destination)); }
步骤二:执行上面的jar包,小编这里写了个各处可用的Util方法
public static boolean validatePDF(String filepath) throws IOException, In terruptedException { String command = String.format( "java -jar C:\\Users\\resources\\contracts\\ImageTester.j ar -k %s -f %s", System.getProperty("applitools.api.key"), filepath); Process process = Runtime.getRuntime().exec(command); process.waitFor(); String stream = IOUtils.toString(process.getInputStream(), "UTF- 8"); System.out.println(stream); if(stream != null && stream.contains("Mismatch")){ return false; } return true;}
步骤三::用断言进行比对测试
Assert.assertTrue("Error validating PDF", validatePDF(destination));
完事收工。
以上就是小编解决此项目中问题的全部心路历程与思路。总结来说,在测试中做自动化的核心意义 在于解决重复的、低生产力的人工工作,让机器赋能工程师们追求更快更全面与更深入的测试。