其他测试框架 测试开发之路 ---- 一切为了效率 (数据恢复--AssertJ 的另类用法)

孙高飞 · 2016年05月31日 · 最后由 回复于 2022年02月08日 · 3820 次阅读

前言

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

原理

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

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 18 条回复 时间 点赞

脏数据确实很烦人,有的时候报到开发那里,结果发现是脏数据的原因,太尴尬了。java 这么强大的开源断言神器,我也找找 python 有没有

监控过程中,是只会监控到当前进程(或线程)对 DB 的操作,还是测试脚本之外的 DB 操作也会被记录呢?

#2 楼 @liufei83 都会被监控到。

数据库内数据量比较大的话,会不会崩溃,内存溢出之类的,楼主最大用了多少数据?

#4 楼 @carol_gao 因为暂时只是在自动化测试中用所以数据是比较小的。如果数据量大的话,我觉得会很慢的

#5 楼 @ycwdaaaa 我觉得也会慢,不过这个思路真好,谢谢啦

#1 楼 @jphtmt 话说,python 有没有啊。。

#7 楼 @1875884881 好像找到一个 python should,但是还没用过

changes.setStartPointNow();开启监控后,一直停留在这里,不能执行下面的方法,会有什么情况导致的?

#9 楼 @cythina 可能是你数据库太大了吧。这个机制性能是瓶颈。数据库不能太大了

#10 楼 @ycwdaaaa 就监控了 3 张表,没有监控整个数据库

孙高飞 [该话题已被删除] 中提及了此贴 06月28日 18:51
孙高飞 论自动化测试脚本的质量与效率 中提及了此贴 08月11日 16:42

楼主在另一文章回复中推荐了 assertJ,转来这篇后深感 assertJ 的强大呀!自己试了下看到产生的记录真的被删除了。但还有个疑问哈,监控数据 diff 还包括了非测试脚本产生的数据,可以做到排除这些非测试脚本产生的数据吗?因为我是直接在公司测试环境上执行脚本,其他测试同事也会往测试环境数据库插入了数据。我感觉我是不是要维护一个只有我自己才会往里面插数据的环境😂

叶子 回复

一般来说是的~~ 你得维护一个你自己专用的数据库。 其实它就是记录数据库前后的状态。 把数据恢复回去罢了

adonisjph 回复

所以 python 有没有呢😂 ,我好像没找到~~

adonisjph 回复

老哥,你找到没有

for 循环的时候卡住了

ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47
adonisjph 回复

老哥,python 的有研究出来吗

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册