DBCompare小工具研发

  测试过程中,经常涉及到数据库表记录的检查,比如案例执行完成后,需检查某几张表insert,update或delete是否正确。面对此类测试场景,测试人员一般都是交易前后分别把表记录导出,然后再做比对检查表记录是否正确。这种测试方法存在两个问题:

1、操作繁琐。
2、表记录过多时,纯靠眼力检查容易出错。

  针对以上问题,我们稍微转换一下思路,让工具来简化测试工作,提升测试效率及准确度。下面笔者将以oracle数据库为例,详述DBCompare小工具的研发过程。
  首先,笔者将工具的使用方法做简单描述,然后再跟各位分享工具的实现思路及相关代码。

数据比对工具.png

  上图为小工具的界面展示,“环境”配置在config.properties文件,配置信息为用户名、密码、ip及端口信息,可灵活配置各个环境参数,如下图所示。
环境配置.png

  选择环境,填写表名,然后获取索引;
(1)“输入索引”为选输框,获取索引失败的话,可手工输入索引;
(2)“查询条件”为选输框,如果“查询条件为空”,则查询全表记录作比较;否则只获取符合查询条件的记录作比较;
(3)点击“交易前查询”按钮;
(4)执行交易或测试脚本;
获取索引.png

交易前查询.png

(5)点击“交易后查询”按钮,自动打开交易前后比对文件。

  • 对于交易前和交易后都存在的记录,根据索引比对各字段是否一致,不一致的字段标注红色;
  • 对于交易前存在记录,交易后不存在记录(删除),标准玫瑰色;
  • 对于交易前不存在记录,交易后存在记录(新增),标准红色;
    交易前后记录比对文件.png

    交易前后记录比对文件.png

      以上就是工具的简单描述,接下来咱们来聊聊工具实现的思路。
  • 读取环境配置;
    /*
     * 获取环境配置信息
     */
    public static Map<String, String> getEnv() {
        Map<String, String> env_map = new HashMap<String, String>();
        InputStream in = null;
        Properties p = new Properties();
        try {
            in = new BufferedInputStream(new FileInputStream("config\\"
                    + "config.properties"));
            p.load(in);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        Enumeration<Object> keys = p.keys();
        while (keys.hasMoreElements()) {
            String key = (String) keys.nextElement();
            env_map.put(key, p.getProperty(key));
        }
        System.out.println("env_map: " + env_map);
        return env_map;
    }
  • 链接数据库;
/*
     * 链接数据库
     */
    // @SuppressWarnings("finally") String env,String user,String password
    public static Connection oracle() {
        try {
            /*
             * 获取环境参数
             * upi[0]=用户名;
             * upi[1]=密码;
             * upi[2]=IP地址;
             */
            String usPassIp = envVar_map.get(comBoxItem).trim(); //获取用户名,密码,ip
            String upi[] = usPassIp.split(",");// 分割字符串得到数组
            for (int i = 0; i < upi.length; i++) {
                System.out.println(upi[i]);
            }
            
            Class.forName("oracle.jdbc.driver.OracleDriver");// 加载Oracle驱动程序
            System.out.println("开始尝试连接数据库!");
            String url = "jdbc:oracle:" + "thin:@"+ upi[2];  //10.232.132.6:1521:p2b";
            String user = upi[0];// 用户名,系统默认的账户名
            String password = upi[1];// 你安装时选设置的密码
            con = DriverManager.getConnection(url, user, password);// 获取连接
            System.out.println("连接成功!");
            System.out.println("con前:" + con);

            return con;
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, e.getMessage(), "message",
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }
    }
  • 获取索引;
/*
     * 获取主键字段
     */
    public static ArrayList<String> getKeyWord(String tableName)
            throws SQLException {
        try {
            ResultSet rs = null;
            
            con = oracle();
            if(con == null){  //防止oracle链接报错时,连续提示两次错误框;
                return null;
            }
            
            DatabaseMetaData dbMeta = con.getMetaData();
            

            rs = dbMeta.getPrimaryKeys(null, null, tableName.toUpperCase());

            ArrayList<String> keyWord = new ArrayList<String>();
            while (rs.next()) {
                keyWord.add(rs.getObject(4).toString());
            }
            System.out.println("主键: " + keyWord);

            if (rs != null)
                rs.close();
            if (con != null)
                con.close();
            System.out.println("数据库连接已关闭!");
            System.out.println("con后(主键):" + con);
            return keyWord;
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, e.getMessage(), "message",
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }

    }
  • 交易前查询按钮主要动作是把数据库表的记录临时存放到List<Map<String, String>>,后续再跟交易后查询的记录做比对。
/*
     * 获取交易前数据
     */
    public static List<Map<String, String>> getTradeBef(String tableName,
            String queryConditon) throws SQLException {
        try {
            ResultSet result = null;// 创建一个结果集对象
            PreparedStatement pre = null;// 创建预编译语句对象,一般都是用这个而不用Statement
            String sql = "";

            List<Map<String, String>> tradeBef = new ArrayList<Map<String, String>>();
            ArrayList<String> tableHead = new ArrayList<String>();

            tableHead = getTableHead(tableName);
            if(tableHead == null){  //防止oracle链接报错时,连续提示两次错误框;
                return null;
            }
            
            if (!queryConditon.equals(""))
                sql = "select * from " + tableName + " where " + queryConditon;
            else
                sql = "select * from " + tableName;

            con = oracle();
            if(con == null){  //防止oracle链接报错时,连续提示两次错误框;
                return null;
            }
            
            pre = con.prepareStatement(sql);// 实例化预编译语句
            result = pre.executeQuery();

            while (result.next()) {
                Map<String, String> map = new HashMap<String, String>();
                for (int i = 0; i < tableHead.size(); i++) {
                    map.put(tableHead.get(i),
                            result.getString(tableHead.get(i)));
                }

                tradeBef.add(map); // 交易前
            }
            System.out.println("交易前map: " + tradeBef);
            /*
             * System.out.println("交易前第一个map的商户名: " +
             * tradeBef.get(0).get("MERCHANT_NAME"));
             */

            if (result != null)
                result.close();
            if (pre != null)
                pre.close();
            if (con != null)
                con.close();
            System.out.println("数据库连接已关闭!");
            System.out.println("con后(交易前):" + con);

            JOptionPane.showMessageDialog(null, "交易前查询成功", "message", 1); // 提示交易前查询成功

            return tradeBef;
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, e.getMessage(), "message",
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }

    }
  • 交易后查询按钮的主要动作是把数据库的记录跟交易前获取list<map>做对比,然后写入excel结果文件,对前后不一致的字段标注相应的颜色。
    /*
     * 获取交易后数据
     */
    public static List<Map<String, String>> getTradeAft(String tableName,
            String queryConditon) throws SQLException {
        try {
            ResultSet result = null;// 创建一个结果集对象
            PreparedStatement pre = null;// 创建预编译语句对象,一般都是用这个而不用Statement
            String sql = "";

            List<Map<String, String>> tradeAft = new ArrayList<Map<String, String>>();
            ArrayList<String> tableHead = new ArrayList<String>();

            tableHead = getTableHead(tableName);
            if(tableHead == null){  //防止oracle链接报错时,连续提示两次错误框;
                return null;
            }

            if (!queryConditon.equals(""))
                sql = "select * from " + tableName + " where " + queryConditon;
            else
                sql = "select * from " + tableName;

            con = oracle();
            if(con == null){  //防止oracle链接报错时,连续提示两次错误框;
                return null;
            }
            
            pre = con.prepareStatement(sql);// 实例化预编译语句
            result = pre.executeQuery();

            while (result.next()) {
                Map<String, String> map = new HashMap<String, String>();
                for (int i = 0; i < tableHead.size(); i++) {
                    map.put(tableHead.get(i),
                            result.getString(tableHead.get(i)));
                }

                tradeAft.add(map); // 交易前
            }
            System.out.println("交易后map: " + tradeAft);
            /*
             * System.out.println("交易后第一个map的商户名: " +
             * tradeAft.get(0).get("MERCHANT_NAME"));
             */

            if (result != null)
                result.close();
            if (pre != null)
                pre.close();
            if (con != null)
                con.close();
            System.out.println("数据库连接已关闭!");
            System.out.println("con后(交易后):" + con);

            return tradeAft;
        } catch (Exception e) {
            e.printStackTrace();
            JOptionPane.showMessageDialog(null, e.getMessage(), "message",
                    JOptionPane.ERROR_MESSAGE);
            return null;
        }

    }
/*
     * 交易前后数据对比
     */
    public static String compare(List<Map<String, String>> tradeBef,
            List<Map<String, String>> tradeAft, String tableName)
            throws SQLException, WriteException, IOException, BiffException {

        String keyValue1 = "";
        String keyValue2 = "";
        String keyValue3 = "";
        String keyValue4 = "";
        ArrayList<String> tableHead = new ArrayList<String>();
        tableHead = getTableHead(tableName);
        if(tableHead == null){  //防止oracle链接报错时,连续提示两次错误框;
            return "";
        }

        String filePath = cOutputFile(tableHead); // 创建文件

        ArrayList<String> list1 = new ArrayList<String>(); // 临时存储交易前的记录
        ArrayList<String> list2 = new ArrayList<String>(); // 临时存储交易后的记录
        /****************************************************************************/
        /*
         * 1、处理tradeBef存在但tradeAft不存在的记录; 2、处理tradeBef和tradeAft都存在的记录;
         */
        for (int i = 0; i < tradeBef.size(); i++) {
            keyValue1 = "";
            for (int a = 0; a < key.size(); a++) { // 获取交易前记录主键的值
                keyValue1 += tradeBef.get(i).get(key.get(a));
            }
            System.out.println("keyValue1: " + keyValue1);

            for (int k = 0; k < tableHead.size(); k++) {
                list1.add(tradeBef.get(i).get(tableHead.get(k))); // 临时存储交易前的记录
            }

            for (int j = 0; j < tradeAft.size(); j++) { // 遍历交易后的记录
                keyValue2 = "";
                for (int b = 0; b < key.size(); b++) { // 获取交易后记录主键的值
                    keyValue2 += tradeAft.get(j).get(key.get(b));
                }
                System.out.println("keyValue2: " + keyValue2);

                if (keyValue1.equals(keyValue2)) { // 记录在tradeBef和tradeAft都存在
                    for (int k = 0; k < tableHead.size(); k++) {
                        list2.add(tradeAft.get(j).get(tableHead.get(k))); // 临时存储交易后的记录
                    }
                    break;
                }
            }

            if (list2.size() == 0) { // tradeBef存在,tradeAft不存在
                wOutputFile(filePath, list1, null, tableHead); // 写结果文件
            } else {
                wOutputFile(filePath, list1, list2, tableHead);
            }
            System.out.println("交易前list1: " + list1);
            System.out.println("交易后list2: " + list2);
            list1.clear(); // 清除临时记录
            list2.clear();
        }

        /****************************************************************************/
        /*
         * 1、处理tradeBef不存在但tradeAft存在的记录;
         */
        for (int i = 0; i < tradeAft.size(); i++) {
            keyValue3 = "";
            for (int a = 0; a < key.size(); a++) { // 获取交易后记录主键的值
                keyValue3 += tradeAft.get(i).get(key.get(a));
            }

            for (int k = 0; k < tableHead.size(); k++) {
                list2.add(tradeAft.get(i).get(tableHead.get(k))); // 临时存储交易后的记录
            }

            for (int j = 0; j < tradeBef.size(); j++) { // 遍历交易前的记录
                keyValue4 = "";
                for (int b = 0; b < key.size(); b++) { // 获取交易前记录主键的值
                    keyValue4 += tradeBef.get(j).get(key.get(b));
                }

                if (keyValue3.equals(keyValue4)) {
                    list2.clear(); // 记录在tradeBef和tradeAft都存在,则清空list
                    break; // 跳出循环
                }
            }
            if (list2.size() != 0) { // 交易后记录list非空
                wOutputFile(filePath, null, list2, tableHead);
                System.out.println("tradeBef不存在但tradeAft存在 list2: " + list2);
            }
            list2.clear();
        }
        return filePath;

    }
    /*
     * cOutputFile方法创建对比结果文件,入参为表头list;
     */
    public static String cOutputFile(ArrayList<String> list)
            throws IOException, WriteException {

        String temp_str = "";
        Date dt = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        temp_str = sdf.format(dt); // 获取时间戳

        // 相对路径默认为 D:\Progrom Files\apache-jmeter-3.1\bin "D:\\\\" +
        String filepath = "result\\" + "比对结果" + "_output_" + "_" + temp_str
                + ".xls"; // 以时间戳命名结果文件,确保唯一

        File output = new File(filepath);

        if (!output.isFile()) {
            output.createNewFile(); // 如果指定文件不存在,则新建该文件
            WritableWorkbook writeBook = Workbook.createWorkbook(output);
            WritableSheet Sheet = writeBook.createSheet("比对结果", 0); // createSheet(sheet名称,第几个sheet)

            for (int i = 0; i < list.size(); i++) {
                // CellView cellView = new CellView();
                // cellView.setAutosize(true); //设置自动大小
                // Sheet.setColumnView(i, cellView);//根据内容自动设置列宽

                Sheet.setColumnView(i, 25); // 设置列宽度setColumnView(列号,宽度)
                WritableFont headfont = new WritableFont(
                        WritableFont.createFont("宋体"), 11, WritableFont.BOLD); // 字体样式
                WritableCellFormat headwcf = new WritableCellFormat(headfont);
                headwcf.setBackground(Colour.GRAY_25); // 灰色颜色
                headwcf.setAlignment(Alignment.CENTRE); // 设置文字居中对齐方式;
                headwcf.setVerticalAlignment(VerticalAlignment.CENTRE); // 设置垂直居中;

                Label labe00 = new Label(i, 0, list.get(i), headwcf); // Label(列号,行号,内容)
                Sheet.addCell(labe00);
            }

            writeBook.write();
            writeBook.close();
        }
        return filepath;
    }

    /*
     * wOutputFile方法写对比结果文件,入参为 文件路径、交易前记录list、交易后记录list、表头list;
     */
    public static void wOutputFile(String filepath, ArrayList<String> list1,
            ArrayList<String> list2, ArrayList<String> tableHead)
            throws IOException, RowsExceededException, WriteException,
            BiffException {

        File output = new File(filepath);
        InputStream instream = new FileInputStream(filepath);
        Workbook readwb = Workbook.getWorkbook(instream);
        WritableWorkbook wbook = Workbook.createWorkbook(output, readwb); // 根据文件创建一个操作对象

        WritableSheet readsheet = wbook.getSheet(0);
        int rsRows = readsheet.getRows(); // 获取Sheet表中所包含的总行数

        WritableFont font = new WritableFont(WritableFont.createFont("宋体"), 10,
                WritableFont.NO_BOLD);// 字体样式

        Cell cell1 = readsheet.getCell(0, rsRows);
        if (cell1.getContents().equals("")) {
            for (int m = 0; m < tableHead.size(); m++) {

                WritableCellFormat wcf = new WritableCellFormat(font); // 字体样式设置

                if (list1 != null && list2 != null) { // 交易前后记录都存在
                    if (list1.get(m) != null && list2.get(m) != null) {// 某些字段可能为null,需加判断
                        if (!list1.get(m).toString()
                                .equals(list2.get(m).toString())) {
                            wcf.setBackground(Colour.RED); // 不一致字段标注红色
                        }
                    }
                    Label labetest1 = new Label(m, rsRows, list1.get(m), wcf);
                    readsheet.addCell(labetest1);
                    Label labetest2 = new Label(m, rsRows + 1, list2.get(m),
                            wcf);
                    readsheet.addCell(labetest2);
                }

                if (list1 != null && list2 == null) { // 交易前记录存在,交易后记录不存在
                    wcf.setBackground(Colour.ROSE); // 不一致字段标注红色
                    Label labetest1 = new Label(m, rsRows, list1.get(m), wcf);
                    readsheet.addCell(labetest1);
                }
                if (list1 == null & list2 != null) { // 交易前记录不存在,交易后记录存在
                    wcf.setBackground(Colour.RED); // 不一致字段标注红色
                    Label labetest2 = new Label(m, rsRows, list2.get(m), wcf);
                    readsheet.addCell(labetest2);
                }
            }
        }
        wbook.write();
        wbook.close();
    }

  至此,DBCompare小工具开发完成,觉得对工作有帮助的童鞋,赶紧尝试一下吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容