测试过程中,经常涉及到数据库表记录的检查,比如案例执行完成后,需检查某几张表insert,update或delete是否正确。面对此类测试场景,测试人员一般都是交易前后分别把表记录导出,然后再做比对检查表记录是否正确。这种测试方法存在两个问题:
1、操作繁琐。
2、表记录过多时,纯靠眼力检查容易出错。
针对以上问题,我们稍微转换一下思路,让工具来简化测试工作,提升测试效率及准确度。下面笔者将以oracle数据库为例,详述DBCompare小工具的研发过程。
首先,笔者将工具的使用方法做简单描述,然后再跟各位分享工具的实现思路及相关代码。
上图为小工具的界面展示,“环境”配置在config.properties文件,配置信息为用户名、密码、ip及端口信息,可灵活配置各个环境参数,如下图所示。
选择环境,填写表名,然后获取索引;
(1)“输入索引”为选输框,获取索引失败的话,可手工输入索引;
(2)“查询条件”为选输框,如果“查询条件为空”,则查询全表记录作比较;否则只获取符合查询条件的记录作比较;
(3)点击“交易前查询”按钮;
(4)执行交易或测试脚本;
(5)点击“交易后查询”按钮,自动打开交易前后比对文件。
- 对于交易前和交易后都存在的记录,根据索引比对各字段是否一致,不一致的字段标注红色;
- 对于交易前存在记录,交易后不存在记录(删除),标准玫瑰色;
- 对于交易前不存在记录,交易后存在记录(新增),标准红色;
以上就是工具的简单描述,接下来咱们来聊聊工具实现的思路。 - 读取环境配置;
/*
* 获取环境配置信息
*/
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小工具开发完成,觉得对工作有帮助的童鞋,赶紧尝试一下吧。