基於Java+HttpClient+TestNG的接口自動化測試框架(十)------ 測試步驟過程中的數據處理


  我們在做接口自動化測試的時候,首先先要想想接口的請求是怎樣完成的,或者說,單個的接口請求有哪些要素。以及一些不同請求的不同應對方法。

  簡單來說,一個接口測試有幾個要素。我們需要挨個的羅列出來。

  0)是否運行該測試。(有個開關控制)

  1)請求的url。(必須的)

  2)請求的header。--------這個來說有點點復雜。一些常用的請求頭內容還比較好說。但是,當Content-Type的值有變化的時候,參數需要進行分別的處理。另外,還有一種就是認證需要加Token的情形。

  3)請求參數。--------通常來說,這部分也是有些復雜的,請求參數會多種多樣。當然,目前絕大多數的情況還都是Json和文件。

  4)請求結果的判定--------返回Json字符串的判定。某些請求會出現圖片和文件的下載,這些也都是要注意的。

  5)請求結果的轉存及后續參數的提供--------具體的處理在前面已經講過。

  對於以上的要素,我們可以設計一個Excel表格來進行管理,羅列出我們需要的要素。然后通過讀取該Excel,來自動的進行接口的請求,以及返回內容的判定,從而達到自動化測試的目的。

 

 

 

   對於上面的形式,童鞋們可能有很多疑問。這里不展開說,只是提供一種參考。這種表格,我姑且稱之為case表格。這里,我簡單描述一下這個表格。首先,我們把Excel的內容進行讀入,

run這一列代表是否運行這條測試(可以用Y/N來控制)。

desc這一列代表本條接口測試的說明,即輸入哪個部分,干什么的。

method這個是請求方法,就是我們通常說的get/post什么的。

url這個是具體請求的鏈接,前面不加主機名。因為這個主機名我們可以在xml文件中進行配置。

header這個就是請求頭的信息,在xml中我還會寫一個public Headers的公用請求頭信息。這里其實是為了碰到不同的情況。比如:我們N個請求中如果都是用到Content-Type:application/json,那么我們可以寫在公用的請求頭中。但是,如果某個請求需要單獨加token來訪問,這個時候請求頭上可能要帶上token的信息。這樣的話,就可以在Excel文件中,這一條請求的header中指定這個token。例如:{"Authorization":"Bearer ${sys_token}"}。

param這個是參數,即訪問時需要增加的參數。當然,這里也可以指定上傳的文件。----關於上傳文件,我們之后再詳細討論。

contains這個是我自己自定義的,因為有的時候只需要直接判定返回的內容中是否包含某段內容。如果是這樣的判定(即不考慮jsonpath),就設定為Y,反之為N。

verify為判定內容。如果前面是Y,則輸入具體需要判定的內容。如果是N,則需要按照JSONPath的路徑來指定判定的值。

save這個為存儲變量來用的。假如該條請求的某個返回信息,是下面某個請求需要的,就可以按照這個方式來處理。

  依照上面的描述,在進行Excel讀取的時候,我們需要將各個部分的信息先讀取出來,並最終按照一定的形式,提供一個List,這個List可以看作是對每行Excel需要進行的接口請求,讀取並處理了數據。(這里說的有點抽象,請參考以下的代碼)

package utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.alibaba.fastjson.JSONObject;

public class excelUtil {

    /**
     * 獲取excel表所有sheet數據
     * @param clz
     * @param path
     * @return
     */
    public static <T> List<T> readExcel(Class<T> clz, String path) {
        System.out.println(path);
        if (null == path || "".equals(path)) {
            return null;
        }
        InputStream is;
        Workbook xssfWorkbook;
        try {
            is = new FileInputStream(path);
            if (path.endsWith(".xls")) {
                xssfWorkbook = new HSSFWorkbook(is);
            } else {
                xssfWorkbook = new XSSFWorkbook(is);
            }
            is.close();
            int sheetNumber = xssfWorkbook.getNumberOfSheets();
            List<T> allData = new ArrayList<T>();
            for (int i = 0; i < sheetNumber; i++) {
                allData.addAll(transToObject(clz, xssfWorkbook,
                        xssfWorkbook.getSheetName(i)));
            }
            return allData;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("轉換excel文件失敗:" + e.getMessage());
        }
    }
    
    /**
     * 獲取excel表指定sheet表數據
     * @param clz
     * @param path
     * @param sheetName
     * @return
     */
    public static <T> List<T> readExcel(Class<T> clz, String path,
            String sheetName) {
        if (null == path || "".equals(path)) {
            return null;
        }
        InputStream is;
        Workbook xssfWorkbook;
        try {
            is = new FileInputStream(path);
            if (path.endsWith(".xls")) {
                xssfWorkbook = new HSSFWorkbook(is);
            } else {
                xssfWorkbook = new XSSFWorkbook(is);
            }
            is.close();
            return transToObject(clz, xssfWorkbook, sheetName);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("轉換excel文件失敗:" + e.getMessage());
        }

    }

    private static <T> List<T> transToObject(Class<T> clz,
            Workbook xssfWorkbook, String sheetName)
            throws InstantiationException, IllegalAccessException,
            InvocationTargetException {
        List<T> list = new ArrayList<T>();
        Sheet xssfSheet = xssfWorkbook.getSheet(sheetName);
        Row firstRow = xssfSheet.getRow(0);
        if(null ==firstRow){
            return list;
        }
        List<Object> heads = getRow(firstRow);
        //添加sheetName字段,用於封裝至apiDataBean中,與bean中的字段相匹配。
        heads.add("sheetName");
        Map<String, Method> headMethod = getSetMethod(clz, heads);
        for (int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {
            try {
                Row xssfRow = xssfSheet.getRow(rowNum);
                if (xssfRow == null) {
                    continue;
                }
                T t = clz.newInstance();
                List<Object> data = getRow(xssfRow);
                //如果發現表數據的列數小於表頭的列數,則自動填充為null,最后一位不動,用於添加sheetName數據
                while(data.size()+1<heads.size()){
                    data.add("");
                }
                data.add(sheetName);
                setValue(t, data, heads, headMethod);
                list.add(t);
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return list;
    }

    private static Map<String, Method> getSetMethod(Class<?> clz,
            List<Object> heads) {
        Map<String, Method> map = new HashMap<String, Method>();
        Method[] methods = clz.getMethods();
        for (Object head : heads) {
            // boolean find = false;
            for (Method method : methods) {
                if (method.getName().toLowerCase()
                        .equals("set" + head.toString().toLowerCase())
                        && method.getParameterTypes().length == 1) {
                    map.put(head.toString(), method);
                    // find = true;
                    break;
                }
            }
            // if (!find) {
            // map.put(head, null);
            // }
        }
        return map;
    }
    
    /*
     * 將具體的值確定。
     */
    private static void setValue(Object obj, List<Object> data,
            List<Object> heads, Map<String, Method> methods)
            throws IllegalArgumentException, IllegalAccessException,
            InvocationTargetException {
        for (Map.Entry<String, Method> entry : methods.entrySet()) {
            Object value = "";
            int dataIndex = heads.indexOf(entry.getKey());
            if (dataIndex < data.size()) {
                value = data.get(heads.indexOf(entry.getKey()));
            }
            Method method = entry.getValue();
            Class<?> param = method.getParameterTypes()[0];
            if (String.class.equals(param)) {
                method.invoke(obj, value);
            } else if (Integer.class.equals(param) || int.class.equals(param)) {
                if(value.toString()==""){
                    value=0;
                }
                method.invoke(obj, new BigDecimal(value.toString()).intValue());
            } else if (Long.class.equals(param) || long.class.equals(param)) {    
                if(value.toString()==""){
                    value=0;
                }
                method.invoke(obj, new BigDecimal(value.toString()).longValue());
            } else if (Short.class.equals(param) || short.class.equals(param)) {
                if(value.toString()==""){
                    value=0;
                }
                method.invoke(obj, new BigDecimal(value.toString()).shortValue());
            } else if (Boolean.class.equals(param)
                    || boolean.class.equals(param)) {
                method.invoke(obj, Boolean.valueOf(value.toString())
                        || value.toString().toLowerCase().equals("y"));
            } else if (JSONObject.class.equals(param)
                    || JSONObject.class.equals(param)) {
                method.invoke(obj, JSONObject.parseObject(value.toString()));
            }else {
                // Date
                method.invoke(obj, value);
            }
        }
    }

    private static List<Object> getRow(Row xssfRow) {
        List<Object> cells = new ArrayList<Object>();
        if (xssfRow != null) {
            for (short cellNum = 0; cellNum < xssfRow.getLastCellNum(); cellNum++) {
                Cell xssfCell = xssfRow.getCell(cellNum);
                cells.add(getValue(xssfCell));
            }
        }
        return cells;
    }

    private static String getValue(Cell cell) {
        if (null == cell) {
            return "";            
        } else if (cell.getCellType() == CellType.BOOLEAN) {
            // 返回布爾類型的值
            return String.valueOf(cell.getBooleanCellValue());
        } else if (cell.getCellType() == CellType.NUMERIC) {
            // 返回數值類型的值
            return String.valueOf(cell.getNumericCellValue());
        } else {
            // 返回字符串類型的值
            return String.valueOf(cell.getStringCellValue());
        }
    }
}

  基於上面的Excel讀取,這里我們做成一個TestBase的類,這個類確定了各個參數的數據應該如何處理,並在最終的接口請求中,以何種形式來工作。部分代碼在之前的篇章中已經進行過較為詳細的說明,這里不再贅述,只為參考。請參見:基於Java+HttpClient+TestNG的接口自動化測試框架(四)-------參數存取處理


import java.nio.file.Paths;
import
java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dom4j.DocumentException; import org.testng.Assert; import com.alibaba.fastjson.JSONPath; import bean.ApiDataBean; import bean.baseBean; import utils.assertUtil; import utils.excelUtil; import utils.functionUtil; import utils.reportUtil; import utils.stringUtil; public class TestBase { /** * 公共參數數據池(全局可用) */ private static Map<String, String> saveDatas = new HashMap<String, String>(); /** * 替換符,如果數據中包含“${}”則會被替換成公共參數中存儲的數據 */ protected Pattern replaceParamPattern = Pattern.compile("\\$\\{(.*?)\\}"); /** * 截取自定義方法正則表達式:__xxx(ooo) */ protected Pattern funPattern = Pattern .compile("__(\\w*?)\\((([\\w\\\\\\/:\\.\\$]*,?)*)\\)"); private static Connection myCon; protected void setSaveDatas(Map<String, String> map) { saveDatas.putAll(map); } /** * 組件預參數(處理__fucn()以及${xxxx}) * * @param apiDataBean * @return */ protected String buildParam(String param) { param = getCommonParam(param); Matcher m = funPattern.matcher(param); while (m.find()) { String funcName = m.group(1); String args = m.group(2); String value; // bodyfile屬於特殊情況,不進行匹配,在post請求的時候進行處理 if (functionUtil.isFunction(funcName) && !funcName.equals("bodyfile")) { // 屬於函數助手,調用那個函數助手獲取。 value = functionUtil.getValue(funcName, args.split(",")); // 解析對應的函數失敗 Assert.assertNotNull(value, String.format("解析函數失敗:%s。", funcName)); param = stringUtil.replaceFirst(param, m.group(), value); } } return param; } protected void savePreParam(String preParam) { // 通過';'分隔,將參數加入公共參數map中 if (stringUtil.isEmpty(preParam)) { return; } String[] preParamArr = preParam.split(";"); String key, value; for (String prepar : preParamArr) { if (stringUtil.isEmpty(prepar)) { continue; } key = prepar.split("=")[0]; value = prepar.split("=")[1]; reportUtil.log(String.format("存儲%s參數,值為:%s。", key, value)); saveDatas.put(key, value); } } /** * 取公共參數 並替換參數,處理${} * @param param * @return */ protected String getCommonParam(String param) { if (stringUtil.isEmpty(param)) { return ""; } Matcher m = replaceParamPattern.matcher(param);// 取公共參數正則 while (m.find()) { String replaceKey = m.group(1); // 如果公共參數池中未能找到對應的值,該用例失敗。 Assert.assertNotNull(replaceKey, String.format("格式化參數失敗,公共參數中找不到%s。", replaceKey)); String value; // 從公共參數池中獲取值 value = getSaveData(replaceKey); //如果值不為空,則將參數替換為公共參數池里讀取到的value的值。 if(null != value) { param = param.replace(m.group(), value); //如果值為空,則將參數替換為字符串“null” }else { param = param.replace(m.group(), "null"); } } return param; } /** * 獲取公共數據池中的數據 * * @param key * 公共數據的key * @return 對應的value */ protected String getSaveData(String key) { if ("".equals(key) || !saveDatas.containsKey(key)) { return null; } else { return saveDatas.get(key); } } /* * 判定在verfiy里面的結果。 */ protected void verifyResult(String sourceData, String verifyStr, boolean contains) { if (stringUtil.isEmpty(verifyStr)) { return; } //將判定字符串中的定義變量全部換成實際值。 String allVerify = getCommonParam(verifyStr); reportUtil.log("驗證數據:" + allVerify); //是否判定包含。 if (contains) { // 驗證結果包含 assertUtil.contains(sourceData, allVerify); } else { // 通過';'分隔,通過jsonPath進行一一校驗 Pattern pattern = Pattern.compile("([^;]*)=([^;]*)"); Matcher m = pattern.matcher(allVerify.trim()); while (m.find()) { String actualValue = getBuildValue(sourceData, m.group(1)); String exceptValue = getBuildValue(sourceData, m.group(2)); reportUtil.log(String.format("驗證結果,實際值為:%s,期待值為:%s", actualValue, exceptValue)); Assert.assertEquals(actualValue, exceptValue, "驗證預期結果失敗。"); } } } /** * 獲取格式化后的值 * * @param sourchJson * @param key * @return */ private String getBuildValue(String sourchJson, String key) { key = key.trim(); Matcher funMatch = funPattern.matcher(key); if (key.startsWith("$.")) {// jsonpath Object x = JSONPath.read(sourchJson, key);
       //為了處理x為空的情況,沒有比較好的方法,只能簡單處理一下
if(x == null) { key = (String)x; }else { key = x.toString(); } } else if (funMatch.find()) {
        //處理函數的部分 String args = funMatch.group(2); String[] argArr = args.split(","); for (int index = 0; index < argArr.length; index++) { String arg = argArr[index]; if (arg.startsWith("$.")) { argArr[index] = JSONPath.read(sourchJson, arg).toString(); } } String value = functionUtil.getValue(funMatch.group(1), argArr); key = stringUtil.replaceFirst(key, funMatch.group(), value); } return key; } /** * 提取json串中的值保存至公共池中 * * @param json * 將被提取的json串。 * @param allSave * 所有將被保存的數據:aa=$.jsonpath.AA;bb=$.jsonpath.BB,將$.jsonpath. * AA提取出來的值存放至公共池的aa中,將$.jsonpath.BB提取出來的值存放至公共池的bb中 */ protected void saveResult(String json, String allSave) { if (null == json || "".equals(json) || null == allSave || "".equals(allSave)) { return; } allSave = getCommonParam(allSave); String[] saves = allSave.split(";"); String key, value; for (String save : saves) { Pattern pattern = Pattern.compile("([^;=]*)=([^;]*)"); Matcher m = pattern.matcher(save.trim()); while (m.find()) { key = getBuildValue(json, m.group(1)); value = getBuildValue(json, m.group(2)); reportUtil.log(String.format("存儲公共參數 %s值為:%s.", key, value)); saveDatas.put(key, value); } } } /** * 根據配置讀取測試用例,可讀取多個Excel的多個Sheet * * @param clz * 需要轉換的類 * @param excelPaths * 所有excel的路徑配置 * @param excelName * 本次需要過濾的excel文件名 * @param sheetName * 本次需要過濾的sheet名 * @return 返回數據 * @throws DocumentException */ protected <T extends baseBean> List<T> readExcelData(Class<T> clz, String[] excelPathArr, String[] sheetNameArr) throws DocumentException { List<T> allExcelData = new ArrayList<T>();// excel文件數組 List<T> temArrayList = new ArrayList<T>(); for (String excelPath : excelPathArr) { File file = Paths.get(System.getProperty("user.dir"), excelPath).toFile(); temArrayList.clear(); if (sheetNameArr.length == 0 || sheetNameArr[0] == "") { temArrayList = excelUtil.readExcel(clz, file.getAbsolutePath()); } else { for (String sheetName : sheetNameArr) { temArrayList.addAll(excelUtil.readExcel(clz, file.getAbsolutePath(), sheetName)); } } temArrayList.forEach((bean) -> { bean.setExcelName(file.getName()); }); allExcelData.addAll(temArrayList); // 將excel數據添加至list } return allExcelData; } /** * 從MySql數據庫中讀取測試用例 * * @param clz * 需要轉換的類 * @param mysqlStr * 所需要執行的sql語句 * @return 返回數據 * @throws Exception */ @SuppressWarnings("unchecked") protected static <T extends ApiDataBean> List<T> readMySql (Class<T> clz,String mysqlStr) throws Exception{ List<T> dataList = new ArrayList<T>(); String mydriver = "com.mysql.jdbc.Driver"; //防止SSL報錯信息,在連接之后加上?autoReconnect=true&useSSL=false String myUrl = "jdbc:mysql://localhost:3306/apidata?autoReconnect=true&useSSL=false"; String user = "root"; String password = "123456"; try { Class.forName(mydriver); }catch (Exception e) { e.printStackTrace(); } try { myCon = DriverManager.getConnection(myUrl, user, password); }catch(Exception e){ e.printStackTrace(); } if(myCon == null) { reportUtil.log(myUrl +"----------數據庫連接失敗"); } try { Statement st1 = myCon.createStatement(); ResultSet rs = st1.executeQuery(mysqlStr); while(rs.next()) { ApiDataBean adb = new ApiDataBean(); adb.setRun(rs.getBoolean("run")); adb.setDesc(rs.getString("desc")); adb.setMethod(rs.getString("method")); adb.setUrl(rs.getString("url")); adb.setHeader(rs.getString("header")); adb.setParam(rs.getString("param")); adb.setContains(rs.getBoolean("contains")); adb.setVerify(rs.getString("verify")); adb.setSave(rs.getString("save")); dataList.add((T) adb); } } catch (SQLException e) { e.printStackTrace(); } return dataList; } }

         在上面的這段代碼中,我們可以看到對應每個部分的參數對應的一些處理方式。具體來說,在讀取了Excel,並將其存儲起來之后,對於請求參數/判定/保存變量參數方面的處理。可以根據實際的需要,在以上的代碼中進行擴展。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM