引言
项目上线前,一般都要求测试经理提供测试报告。虽说每个项目实现的需求不尽一样,但测试报告的模板往往是一致的,且大多是word格式的。接下来介绍一个word操作神器—Jacob,用于自动生成测试报告。
Jacob介绍
JACOB is a JAVA-COM Bridge that allows you to call COM Automation components from Java. It uses JNI to make native calls to the COM libraries. JACOB runs on x86 and x64 environments supporting 32 bit and 64 bit JVMs.
注意事项
- jdk如果是32位,则需将jacob-1.18-x86.dll放到C:\Windows\System32目录下,64位则将jacob-1.18-x64.dll 放到C:\Windows\SysWOW64目录下。
ps:java -version可查看jdk位数。 - 将jacob-XXX.dll放到 jdk安装目录的jre\bin目录下。
实现步骤
1、 定义测试报告模板
测试报告模板
密码:e6iz59
2、分析案例执行结果文件,填充测试报告模板
根据案例执行结果文件分析缺陷信息及案例执行状态。
public static Map<String,String> countBug(String caseBugFilePath) throws BiffException, IOException{
Map<String,String> testModelMap = new HashMap<String,String>();
InputStream instream = new FileInputStream(caseBugFilePath);
Workbook rwb = Workbook.getWorkbook(instream);
Sheet sheet = rwb.getSheet(0);
int rsRows = sheet.getRows(); // 获取总行数,案例数为总行数-1
int caseNum = rsRows -1;
int caseExcute = 0; //已执行的用例(出去执行结果是“未执行”的用例)
String testModel ="";
if(!sheet.getCell(1, 1).getContents().equals(""))
testModel = sheet.getCell(1, 1).getContents(); //第2列第2行,初始化功能点描述
else
JOptionPane.showMessageDialog(null, "【案例执行结果文件有误,请检查】", "message",JOptionPane.ERROR_MESSAGE);
int caseNumTemp = 0;
StringBuffer buffer = new StringBuffer();
for (int i = 1; i < rsRows; i++) { //第一行为表头
String excuteResult = sheet.getCell(8, i).getContents(); // 第9列第i行,执行结果
if(!excuteResult.trim().equals("未执行"))
caseExcute++; //统计已执行的用例总数
if(sheet.getCell(1, i).getContents().equals(testModel)){
caseNumTemp++; //模块对应的案例数
String bugMess = sheet.getCell(10, i).getContents(); // 第11列第i行,缺陷信息
if(!bugMess.equals(""))
buffer.append(bugMess); //提取缺陷信息
testModelMap.put(testModel, caseNumTemp+":"+buffer.toString()); //key为 模块名,value为 案例数,缺陷信息
}else{
caseNumTemp = 1;
buffer.delete(0, buffer.length()); //清空buffer信息
String bugMess = sheet.getCell(10, i).getContents(); // 第11列第i行,缺陷信息
if(!bugMess.equals(""))
buffer.append(bugMess); //提取缺陷信息
testModel = sheet.getCell(1, i).getContents().trim(); //重新赋值给testModel
testModelMap.put(testModel, caseNumTemp+":"+buffer.toString()); //key为 模块名,value为 案例数,缺陷信息
}
}
testModelMap.put("${caseNum}", caseNum +""); //用例总数
testModelMap.put("${caseExcute}", caseExcute+""); //已执行的用例
System.out.println("用例总数:" + caseNum);
System.out.println("已执行的用例数:" + caseExcute);
System.out.println("模块信息:\n" + testModelMap);
return testModelMap;
}
替换测试报告模板定义的变量、插入附件、生成目录。
public class TestReport {
public static String report(String systemName,String author,String projectName,String caseBugFilePath) throws IOException, BiffException{
Map<String,String> map = new HashMap<String,String>();
/*
* map规则为:key=模块,value=案例数:缺陷信息
* {模块1=49:GYL-1859_[关闭] GYL-1865_[关闭] GYL-1861_[关闭] GYL-1866_[关闭] , 模块2=188:}
*/
map = BugCount.countBug(caseBugFilePath); //统计缺陷及案例信息
JacobFunction m = new JacobFunction(false);
m.createNewDocument();
try{
File file1 = new File("template\\测试报告模板.doc");
File file1_temp = new File("template\\测试报告模板temp.doc");
m.copyFile(file1,file1_temp); //拷贝文件,防止生成测试报告失败,测试报告模板.doc被改写
File file2 = new File("testReport\\"+systemName+"_"+projectName+"_测试报告"+".doc");
m.openDocument(file1_temp.getAbsolutePath());
//m.moveStart();
Date dt=new Date();
SimpleDateFormat matter1=new SimpleDateFormat("yyyy-MM-dd");
m.replaceAllText("${date}",matter1.format(dt)); //当前日期
m.replaceAllText("${author}",author); //作者
m.replaceAllText("${project}",projectName); //项目名称
System.out.println("用例总数:"+ map.get("${caseNum}"));
m.replaceAllText("${caseNum}",map.get("${caseNum}")); //用例总数
System.out.println("已执行的用例:"+ map.get("${caseExcute}"));
m.replaceAllText("${caseExcute}",map.get("${caseExcute}")); //已执行的用例
int modelNum = map.size()-2; //除去 用例总数和已执行额用例,剩下为功能模块数
m.replaceAllText("${modelNum}",modelNum+""); //功能模块数
int i = 1;
int caseNumPerModel = 0; //每个功能模块对应的案例数
int bugClosePerModel = 0; //每个功能模块 关闭的缺陷数
int bugHupPerModel = 0; //每个功能模块 挂起的缺陷数
int bugOtherPerModel = 0; //每个功能模块 其他状态的缺陷数
int bugCloseTotal = 0; //关闭缺陷数 总数
int bugHupTotal = 0; //挂起缺陷数 总数
int bugOtherTotal = 0; //其他状态缺陷数 总数
for(String key:map.keySet()){
if(!key.equals("${caseNum}") && !key.equals("${caseExcute}")){
m.addLastTableRow(6); //表格增加一行
m.putTxtToCell(6,1+i,1,key); //填充功能模块名称
m.putTxtToCell(6,1+i,4,"100%"); //填充覆盖率
String[] array = map.get(key).split(":");
caseNumPerModel = Integer.parseInt(array[0]);
m.putTxtToCell(6,1+i,2,caseNumPerModel+""); //填充规则点(模块案例数)
m.putTxtToCell(6,1+i,3,caseNumPerModel+""); //填充已覆盖(模块案例数)
m.addLastTableRow(7); //表格增加一行
m.putTxtToCell(7,1+i,1,key); //填充功能模块名称
if(array.length == 1){ //表示没有缺陷
m.putTxtToCell(7,1+i,2,"0"); //填充每个功能模块 关闭的缺陷数
m.putTxtToCell(7,1+i,3,"0"); //填充每个功能模块 挂起的缺陷数
m.putTxtToCell(7,1+i,4,"0"); //填充每个功能模块 其他状态的缺陷数
}else{
String array1[] = array[1].toString().trim().split(" ");
for(int j =0;j<array1.length;j++){
if(array1[j].indexOf("关闭") >= 0 ){
bugClosePerModel++; //每个模块关闭的缺陷数
bugCloseTotal++;
}
else
if(array1[j].indexOf("缺陷挂起") >= 0){
bugHupPerModel++; //每个模块 缺陷挂起数
bugHupTotal++;
}
else{
bugOtherPerModel++; //每个模块 其他的状态缺陷数
bugOtherTotal++;
}
}
m.putTxtToCell(7,1+i,2,bugClosePerModel+""); //填充每个功能模块 关闭的缺陷数
m.putTxtToCell(7,1+i,3,bugHupPerModel+""); //填充每个功能模块 挂起的缺陷数
m.putTxtToCell(7,1+i,4,bugOtherPerModel+""); //填充每个功能模块 其他状态的缺陷数
}
i++;
bugClosePerModel =0; //下次循环前再初始化
bugHupPerModel =0; //下次循环前再初始化
bugOtherPerModel =0; //下次循环前再初始化
}
}
m.replaceAllText("${bugCloseTotal}",bugCloseTotal+""); //关闭的缺陷总数
m.replaceAllText("${bugHupTotal}",bugHupTotal+""); //挂起的缺陷总数
m.replaceAllText("${bugOtherTotal}",bugOtherTotal+""); //其他状态的缺陷总数
int bugNum = bugCloseTotal + bugHupTotal + bugOtherTotal;
m.replaceAllText("${bugNum}",bugNum+""); //缺陷总数
System.out.println("功能模块数:"+modelNum);
System.out.println("关闭的缺陷总数:"+ bugCloseTotal);
System.out.println("挂起的缺陷总数:"+ bugHupTotal);
System.out.println("其他状态的缺陷总数:"+ bugOtherTotal);
System.out.println("缺陷总数:"+ bugNum);
File file = new File(caseBugFilePath);
String caseBugFileName = file.getName();
m.replaceFile("${insertCaseBugFile}", caseBugFilePath, caseBugFileName); //插入附件
//m.addLastTableRow(6);
//m.putTxtToCell(6,1,1,"tomandytomandyddddd");
m.createContents("${contents}"); //生成目录
//String[] a = {"s","dd"};
//int b = Integer.parseInt(a[0]) ; //模拟异常
m.save(file2.getAbsolutePath()); //保存测试报告
m.close();
return "success";
}catch (Exception e){
m.close(); //如果生成测试报告异常,则关闭文档,防止报错“测试报告模板temp.doc已被占用”
System.out.println(e.getMessage());
return e.getMessage();
}
}
}
Jacob操作word的各类方法。
public class JacobFunction {
// word文档
private Dispatch doc;
// word运行程序对象
private static ActiveXComponent word;
// 所有word文档集合
private Dispatch documents;
// 选定的范围或插入点
private static Dispatch selection;
private boolean saveOnExit = true;
/** *//**
*
* @param visible 为true表示word应用程序可见
*/
public JacobFunction(boolean visible) { //是否打开word应用程序
if (word == null) {
word = new ActiveXComponent("Word.Application");
word.setProperty("Visible", new Variant(visible));
}
if (documents == null)
documents = word.getProperty("Documents").toDispatch();
}
/** *//**
* 从选定内容或插入点开始查找文本
*
* @param toFindText 要查找的文本
* @return boolean true-查找到并选中该文本,false-未查找到文本
*/
public static boolean find(String toFindText) {
if (toFindText == null || toFindText.equals(""))
return false;
// 从selection所在位置开始查询
Dispatch find = word.call(selection, "Find").toDispatch();
// 设置要查找的内容
Dispatch.put(find, "Text", toFindText);
// 向前查找
Dispatch.put(find, "Forward", "True");
// 设置格式
Dispatch.put(find, "Format", "True");
// 大小写匹配
Dispatch.put(find, "MatchCase", "True");
// 全字匹配
Dispatch.put(find, "MatchWholeWord", "True");
// 查找并选中
//System.out.println("查找");
return Dispatch.call(find, "Execute").getBoolean();
}
/** *//**
* 全局替换文本
*
* @param toFindText 查找字符串
* @param newText 要替换的内容
*/
public void replaceAllText(String toFindText, String newText) {
moveStart(); //移到文件开头
while (find(toFindText)) {
Dispatch.put(selection, "Text", newText);
Dispatch.call(selection, "MoveRight");
moveRight(1);
//System.out.println("替换");
}
}
public void moveRight( int pos) {
if (selection == null )
selection = Dispatch.get(word, "Selection" ).toDispatch();
for ( int i = 0 ; i < pos; i++)
Dispatch.call(selection, "MoveRight" );
}
/** *//**
* 创建一个新的word文档
*
*/
public void createNewDocument() {
doc = Dispatch.call(documents, "Add").toDispatch();
selection = Dispatch.get(word, "Selection").toDispatch();
}
/** *//**
* 打开一个已存在的文档
*
* @param docPath
*/
public void openDocument(String docPath) {
closeDocument();
doc = Dispatch.call(documents, "Open", docPath).toDispatch();
selection = Dispatch.get(word, "Selection").toDispatch();
}
/** *//**
* 文件保存或另存为
*
* @param savePath 保存或另存为路径
* @throws IOException
*/
public void save(String savePath) throws IOException {
File file = new File(savePath);
file.createNewFile();
Dispatch.call(
(Dispatch) Dispatch.call(word, "WordBasic").getDispatch(),
"FileSaveAs", savePath);
}
/** *//**
* 关闭当前word文档
*
*/
public void closeDocument() {
if (doc != null) {
//Dispatch.call(doc, "Save");
Dispatch.call(doc, "Close", new Variant(saveOnExit));
doc = null;
}
}
/** *//**
* 关闭全部应用
*
*/
public void close() {
closeDocument();
if (word != null) {
Dispatch.call(word, "Quit");
word = null;
}
selection = null;
documents = null;
}
/** *//**
* 把插入点移动到文件首位置
*
*/
public void moveStart() {
if (selection == null)
selection = Dispatch.get(word, "Selection").toDispatch();
Dispatch.call(selection, "HomeKey", new Variant(6));
}
/** *//**
*
* @param toFindText 要查找的字符串
* @param imagePath 文件路径
* @return
*/
public boolean replaceFile(String toFindText, String insertFilePath,String fileName) {
moveStart(); //从文件首位置开始
if (!find(toFindText))
return false;
System.out.println("文件路径:"+insertFilePath);
System.out.println("文件名:"+fileName);
Dispatch.call(word, "Run", new Variant("InsertCaseMess"),new Variant(insertFilePath),new Variant(fileName));
return true;
}
/** *//**
* 生成目录
* @param toFindText 要查找的字符串
* @return
*/
public boolean createContents(String toFindText){
moveStart(); //从文件首位置开始
if (!find(toFindText))
return false;
Dispatch alignment = Dispatch.get(selection, "ParagraphFormat")
.toDispatch(); // 行列格式化需要的对象
Dispatch.put(alignment, "Alignment", "1"); // (1:置中 2:靠右 3:靠左)
// insertNewParagraph();
//moveRight(1);
Dispatch range = Dispatch.get(this.selection, "RANGE").toDispatch();
Dispatch fields = Dispatch.call(this.selection, "FIELDS").toDispatch();
Variant call = Dispatch.call(fields,
"ADD",
range,
new Variant(-1),
new Variant("TOC"),
new Variant(true));
Dispatch tablesOfContents = Dispatch.call(doc, "TablesOfContents").toDispatch();// 整个目录区域
// 整个目录
Dispatch tableOfContents = Dispatch.call(tablesOfContents, "Item", new Variant(1)).toDispatch();
// 拿到整个目录的范围
Dispatch tableOfContentsRange = Dispatch.get(tableOfContents, "Range").toDispatch();
// // 取消选中,应该就是移动光标
Dispatch format = Dispatch.get(tableOfContentsRange, "ParagraphFormat").toDispatch();
// // 设置段落格式为首行缩进2个字符
Dispatch.put(format, "CharacterUnitLeftIndent", new Variant(1));
return true;
}
/** *//**
* 在最后1行前增加一行
*
* @param tableIndex
* word文档中的第N张表(从1开始)
*/
public void addLastTableRow(int tableIndex) {
// 所有表格
Dispatch tables = Dispatch.get(doc, "Tables").toDispatch();
// 要填充的表格
Dispatch table = Dispatch.call(tables, "Item", new Variant(tableIndex))
.toDispatch();
// 表格的所有行
Dispatch rows = Dispatch.get(table, "Rows").toDispatch();
Dispatch row = Dispatch.get(rows, "Last").toDispatch();
Dispatch.call(rows, "Add", new Variant(row));
}
/** *//**
* 在指定的单元格里填写数据
*
* @param tableIndex
* @param cellRowIdx
* @param cellColIdx
* @param txt
*/
public void putTxtToCell(int tableIndex, int cellRowIdx, int cellColIdx,
String txt) {
// 所有表格
Dispatch tables = Dispatch.get(doc, "Tables").toDispatch();
// 要填充的表格
Dispatch table = Dispatch.call(tables, "Item", new Variant(tableIndex))
.toDispatch();
Dispatch cell = Dispatch.call(table, "Cell", new Variant(cellRowIdx),
new Variant(cellColIdx)).toDispatch();
Dispatch.call(cell, "Select");
Dispatch.put(selection, "Text", txt);
}
public void copyFile(File fromFile,File toFile) throws IOException{
FileInputStream ins = new FileInputStream(fromFile);
FileOutputStream out = new FileOutputStream(toFile);
byte[] b = new byte[1024];
int n=0;
while((n=ins.read(b))!=-1){
out.write(b, 0, n);
}
ins.close();
out.close();
}
}
3、插入附件。
第2点的代码实现了插入附件的功能,但有以下几点需要关注。以wps为例,录制宏,然后再通过java调用宏来实现插入附件。
首先,使用wps录制一个插入附件的宏,命名为“InsertCaseMess”,步骤如下。
点击“确定”按钮后,再进行“插入附件”操作。
随便插入一个附件,然后“停止录制”。
点击“VB编辑器”,修改“InsertCaseMess脚本”如下并保存。
Sub InsertCaseMess(filePath As String, fileName As String)
'
' InsertCaseMess Macro
' 宏由 lenovo 录制,时间: 2018/05/21
'
'
Selection.InlineShapes.AddOLEObject fileName:=filePath, LinkToFile:=0, DisplayAsIcon:=-1, IconFileName:="C:\Users\lenovo\AppData\Local\Kingsoft\WPS Office\10.1.0.7245\office6\et.exe", IconIndex:=3, IconLabel:=fileName
End Sub
其次,在测试报告模板中定义“${insertCaseBugFile}”变量,通过replaceFile方法把变量替换为附件。
m.replaceFile("${insertCaseBugFile}", caseBugFilePath, caseBugFileName); //插入附件
/** *//**
*
* @param toFindText 要查找的字符串
* @param imagePath 文件路径
* @return
*/
public boolean replaceFile(String toFindText, String insertFilePath,String fileName) {
moveStart(); //从文件首位置开始
if (!find(toFindText))
return false;
System.out.println("文件路径:"+insertFilePath);
System.out.println("文件名:"+fileName);
Dispatch.call(word, "Run", new Variant("InsertCaseMess"),new Variant(insertFilePath),new Variant(fileName));
return true;
}
测试报告
生成报告路径
密码:ghtw49