依賴Jacob自動(dòng)生成測(cè)試報(bào)告

引言

項(xiàng)目上線前,一般都要求測(cè)試經(jīng)理提供測(cè)試報(bào)告。雖說(shuō)每個(gè)項(xiàng)目實(shí)現(xiàn)的需求不盡一樣,但測(cè)試報(bào)告的模板往往是一致的,且大多是word格式的。接下來(lái)介紹一個(gè)word操作神器—Jacob,用于自動(dòng)生成測(cè)試報(bào)告。

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.

注意事項(xiàng)

官網(wǎng)下載地址

  • jdk如果是32位,則需將jacob-1.18-x86.dll放到C:\Windows\System32目錄下,64位則將jacob-1.18-x64.dll 放到C:\Windows\SysWOW64目錄下。
    ps:java -version可查看jdk位數(shù)。
  • 將jacob-XXX.dll放到 jdk安裝目錄的jre\bin目錄下。

實(shí)現(xiàn)步驟

1、 定義測(cè)試報(bào)告模板

測(cè)試報(bào)告模板
密碼:e6iz59

2、分析案例執(zhí)行結(jié)果文件,填充測(cè)試報(bào)告模板

案例執(zhí)行結(jié)果文件(ps:該文件由工具生成)

根據(jù)案例執(zhí)行結(jié)果文件分析缺陷信息及案例執(zhí)行狀態(tài)。

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(); // 獲取總行數(shù),案例數(shù)為總行數(shù)-1
        int caseNum = rsRows -1;
        
        int caseExcute = 0; //已執(zhí)行的用例(出去執(zhí)行結(jié)果是“未執(zhí)行”的用例)
        
        String testModel ="";
        if(!sheet.getCell(1, 1).getContents().equals(""))
            testModel = sheet.getCell(1, 1).getContents();  //第2列第2行,初始化功能點(diǎn)描述
        else
            JOptionPane.showMessageDialog(null, "【案例執(zhí)行結(jié)果文件有誤,請(qǐng)檢查】", "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行,執(zhí)行結(jié)果
            if(!excuteResult.trim().equals("未執(zhí)行"))
                caseExcute++;  //統(tǒng)計(jì)已執(zhí)行的用例總數(shù)

            if(sheet.getCell(1, i).getContents().equals(testModel)){
                caseNumTemp++;  //模塊對(duì)應(yīng)的案例數(shù)
                
                String bugMess = sheet.getCell(10, i).getContents();      // 第11列第i行,缺陷信息
                if(!bugMess.equals(""))
                    buffer.append(bugMess);  //提取缺陷信息
                
                testModelMap.put(testModel, caseNumTemp+":"+buffer.toString()); //key為 模塊名,value為 案例數(shù),缺陷信息
            }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為 案例數(shù),缺陷信息
            }
        }
        
        testModelMap.put("${caseNum}", caseNum +"");      //用例總數(shù)
        testModelMap.put("${caseExcute}", caseExcute+""); //已執(zhí)行的用例
        
        System.out.println("用例總數(shù):" + caseNum);
        System.out.println("已執(zhí)行的用例數(shù):" + caseExcute);
        System.out.println("模塊信息:\n" + testModelMap);
        
        return testModelMap;
    }

替換測(cè)試報(bào)告模板定義的變量、插入附件、生成目錄。

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規(guī)則為:key=模塊,value=案例數(shù):缺陷信息
         * {模塊1=49:GYL-1859_[關(guān)閉] GYL-1865_[關(guān)閉] GYL-1861_[關(guān)閉] GYL-1866_[關(guān)閉] , 模塊2=188:}
         */
        map = BugCount.countBug(caseBugFilePath);  //統(tǒng)計(jì)缺陷及案例信息
        
        JacobFunction m = new JacobFunction(false);
        m.createNewDocument();
        
        try{    
            File file1 = new File("template\\測(cè)試報(bào)告模板.doc");
            File file1_temp = new File("template\\測(cè)試報(bào)告模板temp.doc");
            m.copyFile(file1,file1_temp); //拷貝文件,防止生成測(cè)試報(bào)告失敗,測(cè)試報(bào)告模板.doc被改寫
            
            File file2 = new File("testReport\\"+systemName+"_"+projectName+"_測(cè)試報(bào)告"+".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));   //當(dāng)前日期
            
            m.replaceAllText("${author}",author);        //作者
            m.replaceAllText("${project}",projectName);  //項(xiàng)目名稱
            
            System.out.println("用例總數(shù):"+ map.get("${caseNum}"));
            m.replaceAllText("${caseNum}",map.get("${caseNum}")); //用例總數(shù)
            
            System.out.println("已執(zhí)行的用例:"+ map.get("${caseExcute}"));
            m.replaceAllText("${caseExcute}",map.get("${caseExcute}")); //已執(zhí)行的用例
            
            int modelNum = map.size()-2; //除去 用例總數(shù)和已執(zhí)行額用例,剩下為功能模塊數(shù) 
            m.replaceAllText("${modelNum}",modelNum+""); //功能模塊數(shù)
            
            int i = 1;
            int caseNumPerModel = 0; //每個(gè)功能模塊對(duì)應(yīng)的案例數(shù)
            int bugClosePerModel = 0; //每個(gè)功能模塊 關(guān)閉的缺陷數(shù)
            int bugHupPerModel = 0; //每個(gè)功能模塊 掛起的缺陷數(shù)
            int bugOtherPerModel = 0; //每個(gè)功能模塊 其他狀態(tài)的缺陷數(shù)
            int bugCloseTotal = 0; //關(guān)閉缺陷數(shù) 總數(shù)
            int bugHupTotal = 0;   //掛起缺陷數(shù) 總數(shù)
            int bugOtherTotal = 0; //其他狀態(tài)缺陷數(shù) 總數(shù)
            
            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+"");    //填充規(guī)則點(diǎn)(模塊案例數(shù))
                    m.putTxtToCell(6,1+i,3,caseNumPerModel+"");    //填充已覆蓋(模塊案例數(shù))
                    
                    m.addLastTableRow(7); //表格增加一行
                    m.putTxtToCell(7,1+i,1,key);    //填充功能模塊名稱
                    
                    if(array.length == 1){ //表示沒(méi)有缺陷
                        m.putTxtToCell(7,1+i,2,"0");    //填充每個(gè)功能模塊 關(guān)閉的缺陷數(shù)
                        m.putTxtToCell(7,1+i,3,"0");    //填充每個(gè)功能模塊 掛起的缺陷數(shù)
                        m.putTxtToCell(7,1+i,4,"0");    //填充每個(gè)功能模塊 其他狀態(tài)的缺陷數(shù)
                        
                    }else{
                        String array1[] = array[1].toString().trim().split(" ");
                        
                        for(int j =0;j<array1.length;j++){
                            if(array1[j].indexOf("關(guān)閉") >= 0 ){
                                bugClosePerModel++; //每個(gè)模塊關(guān)閉的缺陷數(shù)
                                bugCloseTotal++;
                            }
                                
                            else 
                                if(array1[j].indexOf("缺陷掛起") >= 0){
                                    bugHupPerModel++; //每個(gè)模塊 缺陷掛起數(shù)
                                    bugHupTotal++;
                                }
                                else{
                                    bugOtherPerModel++;  //每個(gè)模塊 其他的狀態(tài)缺陷數(shù)
                                    bugOtherTotal++;
                                }   
                        }
                        m.putTxtToCell(7,1+i,2,bugClosePerModel+"");    //填充每個(gè)功能模塊 關(guān)閉的缺陷數(shù)
                        m.putTxtToCell(7,1+i,3,bugHupPerModel+"");    //填充每個(gè)功能模塊 掛起的缺陷數(shù)
                        m.putTxtToCell(7,1+i,4,bugOtherPerModel+"");    //填充每個(gè)功能模塊 其他狀態(tài)的缺陷數(shù)
                    }
                    
                    i++;
                    bugClosePerModel =0; //下次循環(huán)前再初始化
                    bugHupPerModel =0;   //下次循環(huán)前再初始化
                    bugOtherPerModel =0; //下次循環(huán)前再初始化
                }  
            }  
            
            m.replaceAllText("${bugCloseTotal}",bugCloseTotal+""); //關(guān)閉的缺陷總數(shù)
            m.replaceAllText("${bugHupTotal}",bugHupTotal+""); //掛起的缺陷總數(shù)
            m.replaceAllText("${bugOtherTotal}",bugOtherTotal+""); //其他狀態(tài)的缺陷總數(shù)
            int bugNum = bugCloseTotal + bugHupTotal + bugOtherTotal;
            m.replaceAllText("${bugNum}",bugNum+""); //缺陷總數(shù)
            
            System.out.println("功能模塊數(shù):"+modelNum);
            System.out.println("關(guān)閉的缺陷總數(shù):"+ bugCloseTotal);
            System.out.println("掛起的缺陷總數(shù):"+ bugHupTotal);
            System.out.println("其他狀態(tài)的缺陷總數(shù):"+ bugOtherTotal);
            System.out.println("缺陷總數(shù):"+ 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());  //保存測(cè)試報(bào)告
            m.close();
            
            return "success";
            
        }catch (Exception e){
            m.close();       //如果生成測(cè)試報(bào)告異常,則關(guān)閉文檔,防止報(bào)錯(cuò)“測(cè)試報(bào)告模板temp.doc已被占用”
            System.out.println(e.getMessage());
            return e.getMessage();
        }
        
    }
}

Jacob操作word的各類方法。

public class JacobFunction {
    
    // word文檔
    private Dispatch doc;

    // word運(yùn)行程序?qū)ο?    private static ActiveXComponent word;

    // 所有word文檔集合
    private Dispatch documents;

    // 選定的范圍或插入點(diǎn)
    private static Dispatch selection;

    private boolean saveOnExit = true; 
    
    /** *//**
     *    
     * @param visible 為true表示word應(yīng)用程序可見(jiàn)
     */
    public JacobFunction(boolean visible) {   //是否打開(kāi)word應(yīng)用程序
            if (word == null) {
                    word = new ActiveXComponent("Word.Application");
                    word.setProperty("Visible", new Variant(visible));
            }
            if (documents == null)
                    documents = word.getProperty("Documents").toDispatch();
    }
    
    
    /** *//**
     * 從選定內(nèi)容或插入點(diǎn)開(kāi)始查找文本
     *    
     * @param toFindText 要查找的文本
     * @return boolean true-查找到并選中該文本,false-未查找到文本
     */
    public static boolean find(String toFindText) {
            if (toFindText == null || toFindText.equals(""))
                    return false;
            // 從selection所在位置開(kāi)始查詢
            Dispatch find = word.call(selection, "Find").toDispatch();
            // 設(shè)置要查找的內(nèi)容
            Dispatch.put(find, "Text", toFindText);
            // 向前查找
            Dispatch.put(find, "Forward", "True");
            // 設(shè)置格式
            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 要替換的內(nèi)容
     */
    public void replaceAllText(String toFindText, String newText) {
            moveStart();   //移到文件開(kāi)頭
            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" ); 
    }
    
    /** *//**
     * 創(chuàng)建一個(gè)新的word文檔
     *    
     */
    public  void createNewDocument() {
            doc = Dispatch.call(documents, "Add").toDispatch();
            selection = Dispatch.get(word, "Selection").toDispatch();
    } 
    
    /** *//**
     * 打開(kāi)一個(gè)已存在的文檔
     *    
     * @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);
    } 
    
    /** *//**
     * 關(guān)閉當(dāng)前word文檔
     *    
     */
    public void closeDocument() {
            if (doc != null) {
                    //Dispatch.call(doc, "Save");
                    Dispatch.call(doc, "Close", new Variant(saveOnExit));
                    doc = null;
            }
    } 
    
    /** *//**
     * 關(guān)閉全部應(yīng)用
     *    
     */
    public void close() {
            closeDocument();
            if (word != null) {
                    Dispatch.call(word, "Quit");
                    word = null;
            }
            selection = null;
            documents = null;
    } 
    
    /** *//**
     * 把插入點(diǎn)移動(dòng)到文件首位置
     *    
     */
    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();  //從文件首位置開(kāi)始
            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();  //從文件首位置開(kāi)始
        if (!find(toFindText))
                return false;
        
        Dispatch alignment = Dispatch.get(selection, "ParagraphFormat")
                .toDispatch(); // 行列格式化需要的對(duì)象
        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();// 整個(gè)目錄區(qū)域

        // 整個(gè)目錄
        Dispatch tableOfContents = Dispatch.call(tablesOfContents, "Item", new Variant(1)).toDispatch();

        // 拿到整個(gè)目錄的范圍
        Dispatch tableOfContentsRange = Dispatch.get(tableOfContents, "Range").toDispatch();
//        // 取消選中,應(yīng)該就是移動(dòng)光標(biāo)
        Dispatch format = Dispatch.get(tableOfContentsRange, "ParagraphFormat").toDispatch();
//        // 設(shè)置段落格式為首行縮進(jìn)2個(gè)字符
        Dispatch.put(format, "CharacterUnitLeftIndent", new Variant(1));
        
        return true;
    }
    
    /** *//**
     * 在最后1行前增加一行
     *    
     * @param tableIndex
     *                        word文檔中的第N張表(從1開(kāi)始)
     */
    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));
    } 
    
    /** *//**
     * 在指定的單元格里填寫數(shù)據(jù)
     *    
     * @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點(diǎn)的代碼實(shí)現(xiàn)了插入附件的功能,但有以下幾點(diǎn)需要關(guān)注。以wps為例,錄制宏,然后再通過(guò)java調(diào)用宏來(lái)實(shí)現(xiàn)插入附件。
首先,使用wps錄制一個(gè)插入附件的宏,命名為“InsertCaseMess”,步驟如下。

錄制宏

錄制宏

點(diǎn)擊“確定”按鈕后,再進(jìn)行“插入附件”操作。
錄制宏

隨便插入一個(gè)附件,然后“停止錄制”。
錄制宏

點(diǎn)擊“VB編輯器”,修改“InsertCaseMess腳本”如下并保存。

Sub InsertCaseMess(filePath As String, fileName As String)
'
' InsertCaseMess Macro
' 宏由 lenovo 錄制,時(shí)間: 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

其次,在測(cè)試報(bào)告模板中定義“${insertCaseBugFile}”變量,通過(guò)replaceFile方法把變量替換為附件。

測(cè)試報(bào)告模板

m.replaceFile("${insertCaseBugFile}", caseBugFilePath, caseBugFileName);  //插入附件
    /** *//**
     *    
     * @param toFindText 要查找的字符串
     * @param imagePath 文件路徑
     * @return
     */
    public boolean replaceFile(String toFindText, String insertFilePath,String fileName) {
            moveStart();  //從文件首位置開(kāi)始
            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;
    }

測(cè)試報(bào)告

生成報(bào)告小工具

生成報(bào)告路徑
密碼:ghtw49

參考資料

Java操作Microsoft Word之jacob
Jacob官網(wǎng)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 測(cè)試現(xiàn)在被普遍認(rèn)為“保證產(chǎn)品質(zhì)量”這個(gè)籠統(tǒng)的說(shuō)法下,而測(cè)試本身是什么呢?今天我們就測(cè)試本身跟大家一起討論。 測(cè)試是...
    西邊人閱讀 4,925評(píng)論 2 52
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評(píng)論 19 139
  • 【背景】: 1、電話銷售,互聯(lián)網(wǎng)廣告推廣,以前做過(guò)面銷。 【問(wèn)題】: 1、學(xué)習(xí)能力差如何快速提高互聯(lián)網(wǎng)方面能力? ...
    雙魚若曦閱讀 1,088評(píng)論 0 0
  • 不得不和你們——我親愛(ài)的家人們說(shuō)再見(jiàn)了,我是個(gè)簡(jiǎn)單的關(guān)燈幾秒鐘就會(huì)睡著,睡著了連夢(mèng)都會(huì)很少做的人,而昨夜,我卻為你...
    有風(fēng)的夏日閱讀 638評(píng)論 29 17

友情鏈接更多精彩內(nèi)容