前言
本来今天的内容没打算放到这个系列里的,但其实这个帖子应该是这个系列的第一篇关于数据管理策略的后续。所以我想了想,还是放在这里面说吧。之前的帖子说过我们有共享数据和隔离数据,并讨论了针对这些数据在框架层面上的处理(如有不清楚的请看这个系列的第一篇帖子)。我们说明了注册式数据管理方式的缺点再于我们只能销毁我们注册进框架中的数据,如果被测功能本身就产生了脏数据的话,我们是没有办法的,只能写代码去删除了。但是最近终于让我找到了克服这个缺陷的方法,也就是框架终于可以监控到测试脚本运行前以及运行后的变化并将数据恢复到运行前的状态。我们再也没有脏数据的忧虑了。这是注册式数据管理的进阶版,我命名为监控式。
原理
其实原理很通俗易懂,就是记录一下case运行前的状态,在case结束的时候再把数据恢复回去。以前有人建议我用docker快速起一个数据库实例来达到数据恢复的目的,我没有实验过效率怎么样。当时想的就是做一个通用的框架,即使没有docker也可以做到。所以只能另辟蹊径,之后发现我们java大名鼎鼎的断言神器AssertJ中有changes的概念,一下子我欣喜若狂,省去我自己去监控数据变化了。 AssertJ的changes概念已经做到了数据库的diff。
AssertJ
AssertJ 是目前java中最强大的断言开源项目了。它提供了各种各样强大的功能,具体的我在另一篇帖子中介绍。今天我们就看一看如何利用它的changes概念达到我们的目的。
BasicDataSource dataSource = SpringContext.getBean("dataSource", BasicDataSource.class);
Changes changes = new Changes(dataSource);
上面是创建一个changes的标准代码,它接受一个参数,java的DataSource。只不过我的项目用的是spring+mybatis构成的持久化层,所以代码就变成了上面那样。其实你也可以用下面的方式
Source source = new Source("jdbc:mysql://172.27.1.219:3306/dango?useUnicode=yes&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull", "write", "!@#$1234%^&*5678ABCDabcd");
Changes changes = new Changes(source);
这样你就已经为监控数据库中数据的变化做好了准备。如果你希望不监控整个数据库,只监控部分表的话。其实也可以这么写
Table dataTable = new Table(dataSource, "data");
Table migrationsTable = new Table(dataSource, "migrations");
Table model_appTable = new Table(dataSource, "model_app");
Changes change = new Changes(user_roleTable,user_metricTable,user_aclTable,userTable
,task_dataTable,role_aclTable,reportTable,project_dataTable,
planTable,projectTable,permissionTable,taskTable,roleTable,
model_app_historyTable,model_appTable,migrationsTable
,dataTable);
通过自定义各个table组建,我们也可以定制个别表的监控。那么接下来怎么做?我只需要在测试前开启监控,测试后关闭监控,然后抽取出数据变化,拼接sql语句,将数据恢复到之前的状态就好了。
methodDestory.setStartPoint();
methodDestory.setEndPoint();
上面第一行代码的意思就是开启监控,第二行就是结束监控。然后我们再调用getChangeList方法。
List changeList = this.changes.getChangesList();
这样我们就可以拿到所有的数据的变化了。之后我们要拼接出各种sql语句。
for(Change change:changeList){
ChangeType type = change.getChangeType();
String tableName = change.getDataName();
if("CREATION".equals(type.name())){
String id = change.getRowAtEndPoint().getValuesList().get(0).getColumnName();
Object value = change.getRowAtEndPoint().getValuesList().get(0).getValue();
String sql = "delete from "+tableName+" where "+id+" = "+value+"";
deletionSQLs.add(sql);
}else if("DELETION".equals(type.name())){
String sql = "insert into "+tableName+" values(";
List valuesList = change.getRowAtStartPoint().getValuesList();
for(Value value : valuesList){
Object columenValue = value.getValue();
sql = sql + "'" +columenValue+"',";
}
sql = sql.substring(0, sql.length()-1);
sql = sql +")";
insertSQLs.add(sql);
}else if("MODIFICATION".equals(type.name())){
String sql = "update "+tableName+" SET ";
List valuesList = change.getRowAtStartPoint().getValuesList();
for(Value value : valuesList){
Object columenValue = value.getValue();
String columnName = value.getColumnName();
sql = sql + columnName +"='"+columenValue+"' ,";
}
sql = sql.substring(0, sql.length()-1);
sql = sql + " where "+valuesList.get(0).getColumnName()+" = "+valuesList.get(0).getValue();
updateSQLs.add(sql);
}
}
可以看到,我们根据不同的change类型拼接出了不同的语句。只要我们在测试结束后执行这些sql就可以恢复数据了。
最终形态 @DataManage(baseData="defaultPlan_Project.xls,defaultTask_Plan_Project.xls",recoveryStrategy=RecoveryStrategy.CLASS)
public class TestCreateProject extends BaseCase {
@Test
public void test(){
this.registerData("defaultProject.xls",true);
System.out.println();
}
可以看到最终我们通过使用一个注解来规定我们的数据恢复策略。CLASS表示当前测试类中所有的用例结束后再恢复数据,METHOD表示每执行一个用例前都会初始化数据,用例结束后恢复数据。
结尾
感谢断言神器AssertJ,不仅丰富了我的断言库,还给我提供了如此强大的功能。越来越发现时刻关注各类开源项目的重要性。有些时候好的解决方案真的只在不经意间就发现了。这里真的不想再强调有些情况下测试数据销毁的重要性了,因为我已然强调了无数遍,我知道一定还会有人喷我的。