一、背景概述
工作中接触到不同的项目测试需求,其中一个是对WEB功能进行测试,采用了Selenium+Junit+Maven+SVN,另一个是对Web Service Json接口进行测试,自己使用C#写了一个自动化测试工具。我希望两者都能生成一样标准格式的测试用例和测试报告,既能展示在Web站点又能得到Excel,于是分别用Java和C#实现了这个工具类--生成自定义Web Html/Excel(CSV)测试用例和测试报告。
二、 Html模板设计
需要替换或追加的内容用变量标示,Html格式自定义,工具类中直接对文本内容进行替换或追加。
两个模板文件ListSample.htm和DetailSample.htm内容分别为:
<html> <head> <title>$ProjectName Test Report</title> </head> <body> <h1>$ProjectName Test Report</h1> <table border="1" cellspacing="1" cellpadding="8" style="border: #000000; border-style: solid; border-width: 1px"> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;" colspan="1">Project Name: </td><td colspan="3">$ProjectName</td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Total: </td><td width="100px">$Total</td> <td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Http Path: </td><td><a href="$HttpPath">$HttpPath</a></td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Passed: </td><td style="color:green;font-weight:bold;">$Passed</td> <td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Code Path: </td><td><a href="$ScriptPath">$ScriptPath</a></td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Failed: </td><td style="color:red;font-weight:bold;">$Failed</td> <td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Test Date: </td><td>$TestDate</td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;" colspan="1">Summary: </td><td colspan="3">$Summary</td></tr> </table> <br /> <table width="90%" border="1" cellspacing="1" cellpadding="8" style="table-layout:fixed;border: #000000; border-style: solid; border-width: 1px "> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"> <td width="15%">CaseID</td> <td width="15%"> TaskName </td> <td width="10%"> TestTime </td> <td> TestSummary</td> <td width="10%"> TestResult</td> <td width="15%"> Comments</td></tr> <!--<tr><td>$CaseID</td><td>$TaskName</td><td>$TestTime</td><td>$TestSummary</td><td style="font-weight:bold;"><a href="$href"><font color="$color">$TestResult</font></a></td><td>$Comments</td></tr>-->
其中:
<tr><td>$CaseID</td><td>$TaskName</td><td>$TestTime</td><td>$TestSummary</td><td style="font-weight:bold;"><a href="$href"><font color="$color">$TestResult</font></a></td><td>$Comments</td></tr>
为需要替换追加的内容。
<html> <head> <title>$TaskName Test Details</title> </head> <body> <h1>$TaskName Test Details</h1> <h3><a href="$href">[[Return>>]]</a></h3> <table border="1" cellspacing="1" cellpadding="8" style="border: #000000; border-style: solid; border-width: 1px "> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Task Name: </td><td>$TaskName</td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Test Time: </td><td>$TestTime</td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Test Summary: </td><td>$TestSummary </td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Test Result: </td><td style="color:$color;font-weight:bold;">$TestResult</td></tr> <tr><td align="right" style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;">Comments: </td><td>$Comments</td></tr> </table> <br /> <table width="90%" border="1" cellspacing="1" cellpadding="8" style="table-layout:fixed;border: #000000; border-style: solid; border-width: 1px "> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td>CaseID</td></tr> <tr><td>$CaseID</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td>TaskName </td></tr> <tr><td>$TaskName</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> Precondition </td></tr> <tr><td>$Precondition</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> Steps</td></tr> <tr><td>$Steps</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> Expects</td></tr> <tr><td>$Expects</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> Results</td></tr> <tr><td>$Results</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> TestResult</td></tr> <tr><td style="color:$color;font-weight:bold;">$TestResult</td></tr> <tr style="BACKGROUND-COLOR:#333399;COLOR:white;font-weight:bold;"><td> Remarks</td></tr> <tr><td>$Remarks</td></tr> </table></body> </html>
三、Java实现
SnapShot类(网页截图):
package com.nhn.platform.qa.cwmtest.Utils; import java.awt.Dimension; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import org.apache.commons.io.FileUtils; import org.openqa.selenium.TakesScreenshot; import org.openqa.selenium.WebDriver; import org.openqa.selenium.OutputType; public class SnapShot { static boolean __Debug = false; static String imageFormat = "png"; // image format static int serialNum = 0; static Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); static void Initilize() { serialNum = 0; } /**************************** * snapShot the whole screen * ****************************/ public static void screenShoot(String dirPath,String picName,String htmlPath) { try { serialNum++; // copy a screen shoot capture to a BufferedImage object screens // hoot BufferedImage screenShoot = (new Robot()).createScreenCapture(new Rectangle(0, 0, (int) d.getWidth(), (int) d.getHeight())); String picPathName = dirPath + "\\images\\" + picName; File dir=new File(dirPath + "\\images"); if(!dir.exists()){ dir.mkdirs(); } File f = new File(picPathName); if (__Debug) { System.out.print("Save File " + picName); } // Write to file ImageIO.write(screenShoot, imageFormat, f); if (__Debug) { System.out.print("..Finished!\n"); } appendSnapShotToLogFile("images/"+picName,htmlPath); } catch (Exception ex) { System.out.println(ex); } } public static void appendSnapShotToLogFile(String imageName,String htmlPath) { String content = ""; content += "<table width=\"90%\"><tr><td>\n"; //content += "<img src=\"" + imageName + "\" width=\"" + EtcIO.logPicWidth + "\" height=\"" + EtcIO.logPicHeight + "\" onclick=\"showPic(this);\" />\n"; content += "<img src=\"" + imageName + "\" />\n"; content += "</td></tr></table>\n"; EtcIO.AppendContent(htmlPath, content); } public static void appendSnapShot(WebDriver driver,String dirPath,String picName,String htmlPath) { File screenShotFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE); try { FileUtils.copyFile(screenShotFile,new File(dirPath + "\\images\\" + picName)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } String content = "\n<br /><a href=\"images/"+picName+"\" target=\"_blank\"><img src=\"images/"+picName+"\" width=\"" + EtcIO.logPicWidth + "\" height=\"" + EtcIO.logPicHeight + "\" onclick=\"showPic(this);\" /></a>"; EtcIO.AppendContent(htmlPath, content); } }
HtmlDoc类:
package com.nhn.platform.qa.cwmtest.Utils; import java.io.File; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import org.openqa.selenium.WebDriver; public class HtmlDoc { protected String DirPath = ""; protected String IndexList = "<tr><td>$CaseID</td><td>$TaskName</td><td>$TestTime</td><td>$TestSummary</td><td style=\"font-weight:bold;\"><a href=\"$href\"><font color=\"$color\">$TestResult</font></a></td><td>$Comments</td></tr>"; protected String IndexModel = ""; protected String DetailModel = ""; protected String IndexFile = ""; protected String DetailFile = ""; protected String CsvFile = ""; protected String ProjectName = "CMS"; protected String HttpPath = "http://127.0.0.1/"; protected String ScriptPath = "http://127.0.0.1/"; protected String TestDate = "2013-01-16"; protected String Summary = ""; protected int Total = 0; protected int Passed = 0; protected int Failed = 0; protected String CaseID = ""; protected String TaskName = ""; protected String TestSummary = ""; protected String TestResult = ""; protected String href = ""; protected String color = ""; protected String Comments = "none"; protected String Precondition = ""; protected String Steps = ""; protected String Expects = ""; protected String Results = ""; protected String Remarks = "none"; public String HomePath; private String testTime; public String getTestTime() { Date now = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("HH.mm.ss"); this.testTime = dateFormat.format(now); return this.testTime; } public HtmlDoc() { this.ProjectName = EtcIO.readValue("HtmlDoc.ProjectName"); this.HomePath = EtcIO.readValue("HtmlDoc.HomePath"); this.IndexModel = EtcIO.readValue("HtmlDoc.IndexModel"); this.DetailModel = EtcIO.readValue("HtmlDoc.DetailModel"); this.ScriptPath = EtcIO.readValue("HtmlDoc.ScriptPath"); Date now = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd"); this.TestDate = dateFormat.format(now); this.DirPath = EtcIO.readValue("HtmlDoc.DirPath") + "\\" + this.TestDate; this.HttpPath = EtcIO.readValue("HtmlDoc.HttpPath") + "/" + this.TestDate; this.IndexFile = this.DirPath + "\\" + "index.html"; this.CsvFile = this.DirPath + "\\" + "test_result.txt"; try { String temp = EtcIO.readValue("HtmlDoc.IndexList"); if (temp != null && temp.equals("")) { this.IndexList = temp; } } catch (Exception e) { } File dirPath = new File(this.DirPath); if (dirPath.exists()) { String distFolder = this.DirPath + ".bak." + this.getTestTime(); try { FileToolkit.moveFile(this.DirPath, distFolder); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } try { dirPath.delete(); Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } dirPath.mkdirs(); String[] search = new String[] { "$ProjectName", "$HttpPath", "$ScriptPath", "$TestDate" }; String[] replace = new String[] { this.ProjectName, this.HttpPath, this.ScriptPath, this.TestDate }; EtcIO.ReplaceContent(this.IndexModel, this.IndexFile, search, replace); } public void InsertHtml(String TaskName, String TestSummary, String TestResult, String Comments, String Precondition, String Steps, String Expects, String Results, String Remarks) { this.Total++; this.CaseID = this.ProjectName + "-TEST-"; if (this.Total < 10) { this.CaseID += "000" + this.Total; } else if (this.Total < 100) { this.CaseID += "00" + this.Total; } else if (this.Total < 1000) { this.CaseID += "0" + this.Total; } else { this.CaseID += this.Total; } this.TestResult = TestResult; this.TestSummary = TestSummary; this.Comments = Comments; if (this.TestResult.trim().toLowerCase().equals("pass")) { this.Passed++; this.color = "GREEN"; } else { this.Failed++; this.color = "RED"; } this.TaskName = TaskName; this.href = this.HttpPath + "/" + TaskName + "-" + this.CaseID + ".html"; this.DetailFile = this.DirPath + "\\" + TaskName + "-" + this.CaseID + ".html"; this.Precondition = Precondition; this.Steps = Steps; this.Expects = Expects; this.Results = Results; this.Remarks = Remarks; String temprow = this.IndexList.replace("$CaseID", this.CaseID) .replace("$TaskName", this.TaskName) .replace("$TestTime", this.getTestTime()) .replace("$TestSummary", this.TestSummary) .replace("$href", this.href).replace("$color", this.color) .replace("$TestResult", this.TestResult) .replace("$Comments", this.Comments); EtcIO.AppendContent(this.IndexFile, temprow); String[] search = new String[] { "$CaseID", "$TaskName", "$TestTime", "$TestSummary", "$color", "$TestResult", "$Comments", "$Precondition", "$Steps", "$Expects", "$Results", "$Remarks", "$href" }; String[] replace = new String[] { this.CaseID, this.TaskName, this.getTestTime(), this.TestSummary, this.color, this.TestResult, this.Comments, this.Precondition, this.Steps, this.Expects, this.Results, this.Remarks, this.HttpPath }; EtcIO.ReplaceContent(this.DetailModel, this.DetailFile, search, replace); EtcIO.AppendContent(this.CsvFile, this.CaseID + "`" + this.TaskName + "`" + this.TestSummary + "`" + this.Precondition + "`" + this.Steps + "`" + this.Expects + "`" + this.getTestTime() + "`" + this.Results + "`" + this.TestResult + "`" + this.Remarks + "\r\n"); } public void CompleteCount() { String[] search = new String[] { "$Total", "$Passed", "$Failed" }; String[] replace = new String[] { "" + this.Total, "" + this.Passed, "" + this.Failed }; EtcIO.ReplaceContent(this.IndexFile, this.IndexFile, search, replace); this.Summary = "PassRate: " + String.format("%.2f", ((double) this.Passed / this.Total) * 100) + "% \t CompleteRate: 100%"; EtcIO.ReplaceContent(this.IndexFile, this.IndexFile, new String[] { "$Summary" }, new String[] { this.Summary }); } public void ScreenCapture() { if (!this.DetailFile.equals("")) { SnapShot.screenShoot(this.DirPath, this.CaseID + ".png", this.DetailFile); } } public void ScreenCapture(WebDriver driver) { if (!this.DetailFile.equals("")) { SnapShot.appendSnapShot(driver, this.DirPath, this.CaseID + ".png", this.DetailFile); } } public void ScreenCapture(String imagePath) { if (!this.DetailFile.equals("")) { SnapShot.appendSnapShotToLogFile(imagePath, this.DetailFile); } } }
InsertHtml(String TaskName, String TestSummary, String TestResult, String Comments, String Precondition, String Steps, String Expects, String Results, String Remarks)
该方法即ListSample.htm插入一条Summary并生成一个DetailSample.htm测试详细文件。
CompleteCount()方法为测试完成后对ListSample.htm替换统计和计算测试通过率。
ScreenCapture()方法为测试过程中截图,截屏或者网页截图,并插入DetailSample.htm测试详细文件中。
同时,会生成一个CSV类似格式的文本文件,自定义分隔符·,可以方便使用Excel打开。
四、 .NET实现
HtmlDoc类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Threading; namespace WugSshLib { public class HtmlDoc { public string DirPath = ""; public string IndexList = "<tr><td>$CaseID</td><td>$TaskName</td><td>$TestTime</td><td>$TestSummary</td><td style=\"font-weight:bold;\"><a href=\"$href\"><font color=\"$color\">$TestResult</font></a></td><td>$Comments</td></tr>"; public string IndexModel = ""; public string DetailModel = ""; public string IndexFile = ""; public string DetailFile = ""; public string CsvFile = ""; public string ProjectName = "CMS"; public string HttpPath = "http://127.0.0.1/"; public string ScriptPath = "http://127.0.0.1/"; public string TestDate = "2013-01-16"; public string Summary = ""; public int Total = 0; public int Passed = 0; public int Failed = 0; public string CaseID = ""; public string TaskName = ""; public string TestSummary = ""; public string TestResult = ""; public string href = ""; public string color = ""; public string Comments = "none"; public string Precondition = ""; public string Steps = ""; public string Expects = ""; public string Results = ""; public string Remarks = "none"; public string TestTime { get { return DateTime.Now.ToLongTimeString(); } } public HtmlDoc(string project,string webdir) { this.ProjectName = project; if (Directory.Exists(webdir)) { this.DirPath = Path.Combine(webdir,project); } else { this.DirPath = Path.Combine(System.Windows.Forms.Application.StartupPath, "nginx/html/" + project ); } if (!Directory.Exists(this.DirPath)) { Directory.CreateDirectory(this.DirPath); } IniFiles inifile = new IniFiles(Path.Combine(System.Windows.Forms.Application.StartupPath, "HtmlDoc.ini")); //IndexList = Path.Combine(this.DirPath, inifile.ReadString("HTMLDOC", "IndexList", IndexList)); IndexModel = Path.Combine(System.Windows.Forms.Application.StartupPath, inifile.ReadString("HTMLDOC", "IndexModel", "ListSample.htm")); DetailModel = Path.Combine(System.Windows.Forms.Application.StartupPath, inifile.ReadString("HTMLDOC", "DetailModel", "DetailSample.htm")); this.ScriptPath = inifile.ReadString("HTMLDOC", "ScriptPath", "http://127.0.0.1/"); this.TestDate = DateTime.Now.ToString("yyyy-MM-dd"); this.HttpPath = inifile.ReadString("HTMLDOC", "HttpPath", "http://127.0.0.1/") + project + "/" + this.TestDate; this.IndexFile = Path.Combine(this.DirPath, this.TestDate, "index.html"); this.CsvFile = Path.Combine(this.DirPath, this.TestDate, "test_result.txt"); if (Directory.Exists(Path.Combine(this.DirPath, this.TestDate))) { Directory.Move(Path.Combine(this.DirPath, this.TestDate), Path.Combine(this.DirPath, this.TestDate) + ".bak" + DateTime.Now.ToString("-HHmmss")); Thread.Sleep(2000); } Directory.CreateDirectory(Path.Combine(this.DirPath, this.TestDate)); string tempmodel = File.ReadAllText(this.IndexModel).Replace("$ProjectName", this.ProjectName).Replace("$HttpPath", this.HttpPath).Replace("$ScriptPath", this.ScriptPath).Replace("$TestDate", this.TestDate); File.AppendAllText(this.IndexFile, tempmodel); } public void InsertHtml(string TaskName, string TestSummary, string TestResult, string Comments, string Precondition, string Steps, string Expects, string Results, string Remarks) { this.Total++; this.CaseID = this.ProjectName + "-TEST-"; if (this.Total < 10) { this.CaseID += "000" + this.Total; } else if (this.Total < 100) { this.CaseID += "00" + this.Total; } else if (this.Total < 1000) { this.CaseID += "0" + this.Total; } else { this.CaseID += this.Total; } this.TestResult = TestResult; this.TestSummary = TestSummary; this.Comments = Comments; if (this.TestResult.Trim().ToLower().Equals("pass")) { this.Passed++; this.color = "GREEN"; } else { this.Failed++; this.color = "RED"; } this.TaskName = TaskName; this.href = this.HttpPath + "/" + TaskName + "-"+this.CaseID+".html"; this.DetailFile = Path.Combine(this.DirPath, this.TestDate, TaskName + "-" + this.CaseID + ".html"); this.Precondition = Precondition; this.Steps = Steps; this.Expects = Expects; this.Results = Results; this.Remarks = Remarks; string temprow = this.IndexList.Replace("$CaseID", this.CaseID).Replace("$TaskName", this.TaskName).Replace("$TestTime", this.TestTime).Replace("$TestSummary", this.TestSummary).Replace("$href", this.href).Replace("$color", this.color).Replace("$TestResult", this.TestResult).Replace("$Comments", this.Comments); File.AppendAllText(this.IndexFile, temprow); string tempdetail = File.ReadAllText(this.DetailModel).Replace("$CaseID", this.CaseID).Replace("$TaskName", this.TaskName).Replace("$TestTime", this.TestTime).Replace("$TestSummary", this.TestSummary).Replace("$color", this.color).Replace("$TestResult", this.TestResult).Replace("$Comments", this.Comments).Replace("$Precondition", this.Precondition).Replace("$Steps", this.Steps).Replace("$Expects", this.Expects).Replace("$Results", this.Results).Replace("$Remarks", this.Remarks).Replace("$href", this.HttpPath); File.AppendAllText(this.DetailFile, tempdetail); File.AppendAllText(this.CsvFile, this.CaseID + "`" + this.TaskName + "`" + this.TestSummary + "`" + this.Precondition + "`" + this.Steps + "`" + this.Expects + "`" + this.TestTime + "`" + this.Results + "`" + this.TestResult + "`" + this.Remarks + "\r\n"); } public void CompleteCount() { string tempindex = File.ReadAllText(this.IndexFile); tempindex = tempindex.Replace("$Total", "" + this.Total).Replace("$Passed", "" + this.Passed).Replace("$Failed", "" + this.Failed); this.Summary = "PassRate: "+Math.Round(((double)this.Passed / this.Total)*100,2) + "% \t CompleteRate: 100%"; tempindex = tempindex.Replace("$Summary", this.Summary); //string file = this.IndexFile.Replace("index.htm", "index.html"); File.WriteAllText(this.IndexFile, tempindex); //File.Delete(this.IndexFile); } } }
同样,InsertHtml(string TaskName, string TestSummary, string TestResult, string Comments, string Precondition, string Steps, string Expects, string Results, string Remarks)函数即ListSample.htm插入一条Summary并生成一个DetailSample.htm测试详细文件。
CompleteCount()函数为测试完成后对ListSample.htm替换统计和计算测试通过率。该处没有使用截图,需要的可以引用WinAPI对屏幕或窗口进行截图。
同时,会生成一个CSV类似格式的文本文件,自定义分隔符·,可以方便使用Excel打开。
五、 效果展示