前言

本来今天的内容没打算放到这个系列里的,但其实这个帖子应该是这个系列的第一篇关于数据管理策略的后续。所以我想了想,还是放在这里面说吧。之前的帖子说过我们有共享数据和隔离数据,并讨论了针对这些数据在框架层面上的处理(如有不清楚的请看这个系列的第一篇帖子)。我们说明了注册式数据管理方式的缺点再于我们只能销毁我们注册进框架中的数据,如果被测功能本身就产生了脏数据的话,我们是没有办法的,只能写代码去删除了。但是最近终于让我找到了克服这个缺陷的方法,也就是框架终于可以监控到测试脚本运行前以及运行后的变化并将数据恢复到运行前的状态。我们再也没有脏数据的忧虑了。这是注册式数据管理的进阶版,我命名为监控式

原理

其实原理很通俗易懂,就是记录一下 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<Change> 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<Value> 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<Value> 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,不仅丰富了我的断言库,还给我提供了如此强大的功能。越来越发现时刻关注各类开源项目的重要性。有些时候好的解决方案真的只在不经意间就发现了。这里真的不想再强调有些情况下测试数据销毁的重要性了,因为我已然强调了无数遍,我知道一定还会有人喷我的。


↙↙↙阅读原文可查看相关链接,并与作者交流