自动化测试--封装JDBCUnit
在进行测试的时候,经常需要对数据库进行操作。我们知道,通过代码与数据库交互,需要以下几步:
1.加载驱动
之前有盆友问我,为什么Selenium操作浏览器的时候,非要下载浏览器驱动?为啥对数据库进行操作的时候,直接加载driver就可以了呢?--------之类稍作解释:浏览器并没有供一个API给java来直接操作浏览器,而是提供了一套API给selenium中的XXXDriver.exe(如Chrome的ChromeDriver)来对自己进行各种操作,此时我们想自动化测试浏览器就要下载selenium官网上的各浏览器驱动,通过这个浏览器驱动再去操作浏览器;而各类数据库,如mySql是提供了一套api直接给java等各种开发语言使用,来直接对数据库进行各种操作。---这种情况下,我们直接加载数据库的driver即可。
驱动加载的工作原理:
Class.forName("com.mysql.jdbc.Driver");
在加载某一 Driver 类时,它应该创建自己的实例并向 DriverManager 注册该实例
在初始化的时候会执行注册当前Driver给DriverManager
驱动加载一次就可了,所以一般写在static程序块中。
2.创建数据库链接
加载驱动之后,就可以通过DriverManager来管理并使用该驱动。通过DriverManager.getConnection()方法来获得一个数据库连接。
遍历所有之前已经注册过的驱动,能成功建立连接的则返回。
3.准备sql
4.创建数据库陈述对象/预编译陈述对象
作者这里选择的是预编译陈述对象,因为可以防止Sql注入。sql注入的在这里不再赘述。
preparedStatement p=connect.preparedStatement(sql);
作用是创建一个参数化的preparedStatement对象参数化的sql语句来发送到数据库。
5.执行sql
一般select使用p.executeQuery()来执行,返回的是一个resultSet的结果集
update、delete、insert使用executeUpdate() ,对于dml语言,返回的就是受影响的行数
6.关闭之前打开的链接、resultSet和PreparedStatement
如果每次操作数据库,都要将上述语句都写一遍,会比较繁琐,代码看起来会比较臃肿。为了方便,通常将其封装为一个帮助类。关于数据库操作的封装:
1.只有查询会返回一个结果集,增删改只会返回受影响的行数---------------可以将增删改合并成一个方法
2.查询之后,我希望将查询结果对应到某个类中。比如我们在做自动化测试的时候,会选择把所有的数据库表映射成我们代码中的类A。此时,我希望从数据库中查询到的数据存储到类A的对象中。----------封装一个select方法,使其将查询结果保存到我们的类对象中。
3.查询之后,我们可能只是看一下结果,并不想把结果存放到某个类的对象。这时,就可能会考虑将查询结果放到一个list列表中。-----------封装一个方法,将我们的查询结果保存到list中
4.获得一个数据库链接的操作,也封装成方法,每次调用就可以返回一个数据库连接。
5.关闭数据库连接、关闭数据集、关闭预编译陈述,在每次数据库操作完成之后,都要去依次关闭。因此考虑也将其封装到一个方法中。
基于上面的考虑,笔者完成了下面的代码:
1.包含了获得数据库连接方法和关闭方法的类
package jdbc; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties; public class jdbConnUnit {
static Connection conn = null;
static {
try {
//加载驱动,此时会自动向driverManager注册该driver的实例
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.out.println("找不到这个类jdbc.driver");
e.printStackTrace();
}
} /*
* 提供一个获取数据库连接的方法
* String properties参数 是数据库配置文件的绝对地址
*/
static Connection getConn(String properties) { try {
//通常将数据库连接信息写在一个配置文件中
//创建一个Properties对象
Properties pro = new Properties();
//创建一个流,怼到我们的配置文件上
FileInputStream stream = new FileInputStream(properties);
//将流加载到配置文件中,相当于缓存下来,之后直接get相关数据即可,不用用一次读一次
pro.load(stream); //getConnection方法会遍历之前加载进来的多有driver,尝试建立数据库连接,如果成功则返回
conn = DriverManager.getConnection(pro.getProperty("url"), pro.getProperty("userName"), pro.getProperty("password")); } catch (FileNotFoundException e) {
System.out.println("配置文件未找到!");
e.printStackTrace();
} catch (IOException e) {
System.out.println("配置文件读取失败!");
e.printStackTrace();
} catch (SQLException e) {
System.out.println("数据库连接创建失败");
e.printStackTrace();
} return conn;
} /*
* 提供一个关闭之前打开的链接的方法
* PreparedStatement pStatement----预编译对象
* ResultSet... set------结果集,对于增删改操作,没有返回结果集,所以这里设计成可变参数列表的形式
*/
static void close(PreparedStatement pStatement,ResultSet... set) {
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
System.out.println("数据库连接关闭失败!");
e.printStackTrace();
}
}
if (pStatement != null) {
try {
pStatement.close();
} catch (SQLException e) {
System.out.println("预编译声明对象关闭失败!");
e.printStackTrace();
}
} for (int i = 0; i < set.length; i++) {
if (set[i] != null) {
try {
set[i].close();
} catch (SQLException e) {
System.out.println("结果集关闭失败!");
e.printStackTrace();
}
}
} } }
2.封装了查询方法和增删改方法的类
package jdbc; import java.io.File;
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap; import org.omg.PortableServer.IdAssignmentPolicy; public class jdbcExecUnit { /*
* 该方法是执行select语句,并将查询到的数据返回到调用者提供的类对象中。
* 该方法是一个泛型方法,泛型方法的声明方式为:修饰符 <T> 返回值类型 方法名(参数列表)
* class<T> class 要存储数据的类
* string properties 存储调用者自己的数据库配置文件
* String sql 存储调用者的sql语句
* String... args 存储调用者sql语句占位符相应的参数值
*/
public static <T> List<T> select2(Class<T> clazz, String properties, String sql, String... args)
throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException { //准备一个预编译的sql语句的对象,用来将预编译的sql发送到数据库
PreparedStatement pStatement = null;
//准备一个结果集,用来存储查询获得的结果
ResultSet DataSet = null;
//调用jdbConnUnit类中getConn的方法来获得一个数据库连接
Connection connection = jdbConnUnit.getConn(properties);
//准备一个List,来存放我们的泛型对象
List<T> list = new ArrayList<T>(); try { // 对sql语句执行预编译,并发送给数据库。可以防止sql注入
pStatement = connection.prepareStatement(sql); // 获取可变参数的个数(对应的是sql语句中占位符的个数)
int length = args.length; /* 利用pStatement.setString方法将参数一次赋值给sql中的占位符
* select Id,RoleName from roles WHERE RoleName =? orRoleName=?,
* 相当于给?占位符的位置填充好参数
*/
for (int i = 0; i < args.length; i++) {
pStatement.setString(i + 1, args[i]);
} // 执行预编译好的sql,获得一个结果集
DataSet = pStatement.executeQuery();
// 获得结果集对象的 列的描述(可以通过它得到表头、数据集的列数)
ResultSetMetaData dataTitle = DataSet.getMetaData();
//获得列数
int length1 = dataTitle.getColumnCount(); // 利用结果集的next方法,将游标指向第一行数据,以便后面将数据取出
while (DataSet.next()) {
// 得到泛型T的对象
T t = clazz.newInstance();
// 获得T所有的属性
Field[] fields = clazz.getDeclaredFields();
/*
* 下面的for,判断了数据的类型,按照从数据库读出来的类型对应存储到对象中
*/
for (int i = 0; i < fields.length; i++) {
//得到对应的域,并获得域名
Field field = fields[i];
String tempFieldName = field.getName();
//将每一个域名与列名进行对比,如果有,则赋值
for (int j = 0; j < length1; j++) {
//得到列名
String columnName = dataTitle.getColumnName(j + 1);
if (tempFieldName.equals(columnName)) {
// 将查询出来的resultSet中数据读取为object类型,再将其转换成和域相同的类型
field.set(t, (field.getType().cast(DataSet.getObject(columnName))));
}
}
} // 下面的for,没有判断数据类型,直接按照String类型存入到成员变量中
/*
* for (int i = 0; i < length1; i++) {
*
* String tempTitle=dataTitle.getColumnName(i+1); //根据表头中的列名来获得对应的属性名称 Field
* field=clazz.getDeclaredField(tempTitle); //给对象t的属性field赋值 field.set(t,
* DataSet.getString(tempTitle));
*
* }
*/ //将每一个t对象加入到list中
list.add(t); } } catch (SQLException e) {
System.out.println("查询语句执行失败!");
e.printStackTrace();
}finally{//这个关闭必须写在finally里面,如果获取的conn或者statement的时候出现了问题,这没有在finally中的话,就调用不到close 方法了,或者conn为null的时候,还会报空指针异常
jdbConnUnit.close(pStatement, DataSet);
} return list; } /* * 该方法是执行查询语句,并将结果保存到map中返回。 * 参数: * String properties----数据库配置文件的绝对地址; * String sql-----要执行的sql语句 * String... args----上述sql语句中如果包含占位符?,就需要传入占位符相应的参数值 */ public static List<Map<String, String>> select(String properties, String sql, String... args) { ResultSet DataSet = null; Connection connection = jdbConnUnit.getConn(properties); List<Map<String, String>> list = new ArrayList<Map<String, String>>(); PreparedStatement pStatement = null; try { // 对sql语句执行预编译,发送给数据库。防止sql注入 pStatement = connection.prepareStatement(sql); // 获取可变参数的个数(对应的是占位符的个数) int length = args.length; // 利用setString方法将参数一次赋值给sql中的占位符 // select Id,RoleName from roles WHERE RoleName =? or // RoleName=?,相当于给?占位符的位置填充好参数 for (int i = 0; i < args.length; i++) { pStatement.setString(i + 1, args[i]); } // 执行预编译好的sql,获得一个结果集 DataSet = pStatement.executeQuery(); // 获得结果集对象列的描述(包括数量、类型、属性) ResultSetMetaData dataTitle = DataSet.getMetaData(); int length1 = dataTitle.getColumnCount(); // 利用结果集的next方法,将光标移动到第一行数据,之后获得这一行的数据 while (DataSet.next()) { //这里的map用来存放一行数据,key为列名,Value为列名对应的值。 //必须在循环内部创建,否则最后的list中每个元素都会指向同一个map,而且由于map的key的不可重复性,后面的值会全部覆盖掉前面的值 Map<String, String> map = new TreeMap<String, String>(); for (int i = 0; i < length1; i++) { map.put(dataTitle.getColumnName(i + 1), DataSet.getString(dataTitle.getColumnName(i + 1))); // System.out.println(dataTitle.getColumnName(i+1)); } list.add(map); } } catch (SQLException e) { System.out.println("查询语句执行失败!"); e.printStackTrace(); } jdbConnUnit.close(pStatement, DataSet); return list; } /* * 该方法提供了增删改的语句的执行,返回受影响的行数 * String properties----数据库配置文件的绝对地址; * String sql-----要执行的sql语句 * String... args----上述sql语句中如果包含占位符?,就需要传入占位符相应的参数值 */ public static int update(String properties, String sql, String... args) { Connection connection = jdbConnUnit.getConn(properties); PreparedStatement pStatement = null; try { // 预编译sql pStatement = connection.prepareStatement(sql); } catch (SQLException e) { System.out.println("sql预编译失败!"); e.printStackTrace(); } for (int i = 0; i < args.length; i++) { try { // 给预编译之后的sql中的参数(占位符的位置)赋值 pStatement.setString(i + 1, args[i]); } catch (SQLException e) { System.out.println("更新sql占位符赋值失败"); e.printStackTrace(); } } int byUpdate = 0; try { // 执行增删改的操作 byUpdate = pStatement.executeUpdate(); } catch (SQLException e) { System.out.println("执行增删改异常"); e.printStackTrace(); } if (byUpdate > 0) { System.out.println("更新增删改操作成功,更新了" + byUpdate + "条数据"); } else { System.out.println("更新操作失败"); } jdbConnUnit.close(pStatement); return byUpdate; } }
3.下面是与数据库表对应的一个类(为了演示,只取出了测试库一个表的映射。这种做法是就是根据ORM--对象关系映射来实现的)
package jdbc; import java.math.BigDecimal; public class Trafficinfos{
//Addition AdultPrice ChildPrice CreateTime Destination
//From IsDeleted StartDate TrafficNo TrafficTypeId TravelGroupId
String Id;
String Addition;
BigDecimal AdultPrice;
String ChildPrice;
String CreateTime;
String Destination;
String From;
String IsDeleted;
String StartDate;
String TrafficNo;
String TrafficTypeId;
String TravelGroupId;
public String getId() {
return Id;
}
public void setId(String id) {
Id = id;
}
public String getAddition() {
return Addition;
}
public void setAddition(String addition) {
Addition = addition;
}
public BigDecimal getAdultPrice() {
return AdultPrice;
}
public void setAdultPrice(BigDecimal adultPrice) {
AdultPrice = adultPrice;
}
public String getChildPrice() {
return ChildPrice;
}
public void setChildPrice(String childPrice) {
ChildPrice = childPrice;
}
public String getCreateTime() {
return CreateTime;
}
public void setCreateTime(String createTime) {
CreateTime = createTime;
}
public String getDestination() {
return Destination;
}
public void setDestination(String destination) {
Destination = destination;
}
public String getFrom() {
return From;
}
public void setFrom(String from) {
From = from;
}
public String getIsDeleted() {
return IsDeleted;
}
public void setIsDeleted(String isDeleted) {
IsDeleted = isDeleted;
}
public String getStartDate() {
return StartDate;
}
public void setStartDate(String startDate) {
StartDate = startDate;
}
public String getTrafficNo() {
return TrafficNo;
}
public void setTrafficNo(String trafficNo) {
TrafficNo = trafficNo;
}
public String getTrafficTypeId() {
return TrafficTypeId;
}
public void setTrafficTypeId(String trafficTypeId) {
TrafficTypeId = trafficTypeId;
}
public String getTravelGroupId() {
return TravelGroupId;
}
public void setTravelGroupId(String travelGroupId) {
TravelGroupId = travelGroupId;
} //toString
@Override
public String toString() {
return "Trafficinfos [Id=" + Id + ", Addition=" + Addition + ", AdultPrice=" + AdultPrice + ", ChildPrice="
+ ChildPrice + ", CreateTime=" + CreateTime + ", Destination=" + Destination + ", From=" + From
+ ", IsDeleted=" + IsDeleted + ", StartDate=" + StartDate + ", TrafficNo=" + TrafficNo
+ ", TrafficTypeId=" + TrafficTypeId + ", TravelGroupId=" + TravelGroupId + "]";
} }
上面就完成了一个对数据库操作的简单封装。提供一个测试类,进行简单的测试:
package jdbc; import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set; public class TestJdbc { public static void main(String[] args)
throws InstantiationException, IllegalAccessException, NoSuchFieldException, SecurityException {
// update执行
String updateSql = "UPDATE trafficinfos set AdultPrice=?,`From`=?;";
jdbcExecUnit.update("D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties", updateSql, "3333",
"yuhangnanyuan"); // select执行,数据存储到map里
String selectSql = "SELECT Id,AdultPrice FROM trafficinfos ;";
List<Map<String, String>> data = jdbcExecUnit.select("D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties",selectSql); for (Map<String, String> map : data) {
Set<String> keySets = map.keySet();
for (String keyset : keySets) {
System.out.println(keyset + "--------" + map.get(keyset));
}
} // select执行,数据存储到类的对象里
String selectSql2 = "SELECT Id,AdultPrice FROM trafficinfos ;";
List<Trafficinfos> list = jdbcExecUnit.<Trafficinfos>select2(Trafficinfos.class,
"D:\\myTest\\jdbc\\src\\main\\resources\\jdbc.Properties", selectSql2);
for (Trafficinfos trafficinfos : list) {
System.out.println(trafficinfos);
} } }
完成上述封装之后,可以打成jar包,提交到maven库上,供其他人使用。
自动化测试--封装JDBCUnit的更多相关文章
- APP 自动化测试封装结构模式
原文出处http://www.toutiao.com/a6268089772108333314/ 做过UI自动化测试同学,都会深深体会几个痛点:维护量大.适配量大.编写代码巨大等.基于这些问题,大家都 ...
- 自动化测试--封装getDriver的方法
在自动化测试的时候,通常都会把最常用的功能封装起来,实现通用性. 该篇博客是实现了getDriver方法的封装. 第一次封装的时候,是使用的传参. @Parameters(value = {" ...
- appium+python 【Mac】UI自动化测试封装框架流程简介 <一>
为了多人之间更方便的协作,那么框架本身的结构和编写方式将变得很重要,因此每个团队都有适合自己的框架.如下本人对APP的UI自动化测试的框架进行进行了简单的汇总.主要目的是为了让团队中的其余人员接手写脚 ...
- appium+python 【Mac】UI自动化测试封装框架介绍 <二>---脚本编写(单设备)
1.单设备的执行很简单,平时可多见的是直接在config中进行配置并进行运行即可.如下: # coding=UTF- ''' Created on // @author: SYW ''' from T ...
- appium+python 【Mac】UI自动化测试封装框架介绍 <七>---脚本编写规范
脚本的使用,注释非常关键,无论自己的后期查看还是别人使用,都可以通过注释很明确的知道代码所表达的意思,明确的知道如何调用方法等等.每个团队均有不同的商定形式来写脚本,因此没有明确的要求和规范来约束.如 ...
- appium+python 【Mac】UI自动化测试封装框架介绍 <五>---脚本编写(多设备)
目的: 通过添加设备号,则自动给添加的设备分配端口,启动对应的appium服务.注意:为了方便,将共用一个配置文件. 1.公共的配置文件名称:desired_caps.yaml platformVer ...
- appium+python 【Mac】UI自动化测试封装框架介绍 <四>---脚本的调试
优秀的脚本调试定位问题具备的特点: 1.方便调试. 2.运行报错后容易定位出现的问题. 3.日志的记录清晰 4.日志可被存储,一般测试结果的分析在测试之后会进行,那么日志的存储将会为后期的分析问题带来 ...
- appium+python 【Mac】UI自动化测试封装框架介绍 <三>---脚本的执行
我自己编写的脚本框架中,所有的脚本执行均放在一个py文件中,此文件作为启动文件执行,包含了运行此文件将执行脚本.分配设备端口.自启appium服务等. 详细的介绍待后期补充.
- Selenium自动化测试Python四:WebDriver封装
WebDriver 封装 欢迎阅读WebDriver封装讲义.本篇讲义将会重点介绍Selenium WebDriver API的封装的概念和方法,以及使用封装进行自动化测试的设计. WebDriver ...
随机推荐
- Mac下更新Vim到最新版本
目前,Mac内置的Vim是7.3版本的,而且还缺少很多功能,下面介绍如何通过源码安装更新最新版本的Vim,同时保留系统内置的Vim. # 下载Vim源代码 git clone https://gith ...
- 【题解】洛谷P2421[NOI2002]荒岛野人 (Exgcd)
洛谷P2421:https://www.luogu.org/problemnew/show/P2421 思路 从洞的最大编号开始增大枚举答案 对于每一个枚举的ans要满足Ci+k*Pi≡Cj+k*Pj ...
- Zookeeper入门开发demo
package CreateGroup; import java.io.IOException; import java.util.List; import java.util.concurrent. ...
- android 网络技术基础学习 (七)
使用httpclient协议访问网络: public class MainActivity extends Activity implements OnClickListener{ public vo ...
- 运行Python
安装好python环境,在Windows系统下运行cmd命令行,是windows提供的命令行模式. 在命令行下,可以执行python进入Python交互式环境,也可以执行python hello.py ...
- FFMPEG系列一:Mac下FFMPEG编译安装配置及使用例子
系统环境:10.13以前系统版本,没有升级到macOS High Sierra.正常情况是直接输入brew install ffmpeg即可安装ffmpeg,但是该过程还是有一些坑需要填. 一.mac ...
- 整理下react中常见的坑
其实有些也不能算是坑,有些是react的规定,或者是react的模式和平常的js处理的方式不同罢了 1.setState()是异步的this.setState()会调用render方法,但并不会立即改 ...
- [oracle]索引与索引表管理
(一)索引的概念 索引是一种与表或簇相关的数据库对象,能够为数据的查询提供快捷的存取路径,减少磁盘I/O,提高检索效率. 索引由索引值及记录相应物理地址的ROWID两个部分构成,并按照索引值有序排列, ...
- Hibernate知识点小结(三)-->一对多与多对多配置
一.多表关系与多表设计 1.多表关系 一对一: 表的设计原则(分表原则): 优化表的性能 基于语意化分表 ...
- React中需要多个倒计时的问题
最近有一个需求是做一个闪购列表,列表中每一个商品都有倒计时,如果每一个倒计时都去生成一个setTimeout的话,一个页面就会有很多定时器,感觉这种做法不是非常好,于是换了一个思路. 思路是这样的,一 ...