测试开发之路 (工具篇)–assertJ-db科普

本帖已被设为精华帖!,

前言

今日我们看看java大名鼎鼎的assertj是怎么做断言的

数据库断言

在实际的测试中我们总是跟业务打交道的。跟业务打交道一般很难避免验证数据库中的东西。尤其在接口测试中,一个常见的例子是你测试一个下单的接口。 接口返回可能就是成功过或者失败。你无法从返回值中判断订单的细节是否创建成功。这时候一般要查询数据库做断言。

demo

来看一下例子。

Request sql = new Request(dataSource,"select * from data where name = '"+data.getName()+"'");
assertThat(sql).row()
.column("name").value().isEqualTo(data.getName())
.column("meta").value().isEqualTo(data.getMeta())
.column("line_number").value().isEqualTo(data.getLineNumber())
.column("size").value().isEqualTo(data.getSize())
.column("type_id").value().isGreaterThan(data.getTypeId())
.column("src_type_id").value().isEqualTo(data.getSrcTypeId());

Request resourceSql = new Request(dataSource,"select * from resource where id = "+createdData.getResourceId()+"");
assertThat(resourceSql).row()
.column("uri").value().isEqualTo(data.getUri())
.column("protocol").value().isEqualTo(data.getProtocol().getValue());

Request sql = new Request(dataSource,"select * from plan where name='"+copyPlanName+"'");
assertThat(sql).as("数据库中没有被保存的plan").hasNumberOfRows(1);

OK,assertJ-db有一个对象叫Request,它是对数据库请求的封装。创建的时候你需要传递一个sql和dataSource(我使用的是mybatis的dataSource)。接着就可以使用assertJ丰富的data flow式API 进行断言了。 篇幅有限,介绍几个常用的使用方法。

元素

assertJ-db 有4个维度的元素用来断言。分别是:

  1. Table:表
  2. column:列
  3. row:行
  4. value:具体的一个字段的值

看我们第一个例子的代码中有如下一段:

Request sql = new Request(dataSource,"select * from data where name = '"+data.getName()+"'");
assertThat(sql).row()
.column("name").value().isEqualTo(data.getName())

这里面我们分别取到了第一行(row()默认取第一行,也可以指定第几行)中的名字叫name的列(column接受string类型的列名参数)的值(value)。因为我们是以Request的方式进行断言,所以就没有table什么事了. 我只举个简单的例子,这4种元素互相可以有各种组合的断言大家可以慢慢的探索。我只列出我个人最长用的使用方式。

compare api

assertJ-db 提供丰富的compare api 来定制我们的断言。 我们看如下的代码:

assertThat(request).row(1)
.value().isEqualTo(2)
.value().isEqualTo(DateValue.of(1981, 10, 12))
.hasValues("2", "1981-10-12", "October", "11", "00:41:08", null)
.value().isEqualTo("1.77").isNotEqualTo("1.78")
.value("size").isNotZero()
.isGreaterThan(1.5).isGreaterThanOrEqualTo(1.77)
.isLessThan(2).isLessThanOrEqualTo(1.77)
.value().isNull()

Request sql = new Request(dataSource,"select * from plan where name='"+copyPlanName+"'");
assertThat(sql).hasNumberOfRows(1);

好有很多的compare api 来为我们的4个元素进行验证。我不一一列举了。 它可以给我们各种维度的断言方式。

Request

在之前的介绍中我们总能看到Request的身影,这是我们最常用的断言模式。直接以一个sql筛选出要断言的数据。如下:

Request sql = new Request(dataSource,"select * from plan where name='"+copyPlanName+"'");
assertThat(sql).hasNumberOfRows(1)
.row()
.column("name").value().isEqualTo(copyPlanName)

Request的目的就是根据sql筛选出我们要断言的数据,然后使用我们上面说的元素和compare api进行断言的工作。

Changes

之前我写监控式数据管理策略的时候写过Changes的使用。现在我再说一下吧。按惯例先看例子

public static Changes initChanges() {
Changes change = null;
if(changes != null) {
change = changes;
} else if(tables != null) {
change = new Changes(tables);
} else if(dataSource != null) {
change = new Changes(dataSource);
} else {
if(source == null) {
throw new ChangesException("you have not regiter any source to create changes. you should register changes, datasource,source or tables so that you can init changes to handle test datas");
}
change = new Changes(source);
}
return change;
}

上面是我的数据管理部分初始化changes的代码。可以看到我们可以把dataSource传进去用来diff整个数据库。 也可以把4重元素的table传进去用来diff某一个或某一些表。changes有两个方法,一个叫start point,用来记录初始数据的状态,另一个叫end point。用来记录结束的数据的状态。 changes就是去diff这两个时间点的数据变化。不多说直接上一个例子:

Changes changes = new Changes(dataSource);
changes.setStartPointNow(); // Start point (the moment when the changes start to be taken into account)
makeChangesInTheData();
changes.setEndPointNow(); // End point (the moment when the changes stop to be taken into account)

assertThat(changes)
.change() // First change
.isCreation() // Assertion on the first change
.rowAtStartPoint() // Row at the start point
.doesNotExist() // Assertion on the row at start point of the first change
.returnToChange() // Go back to the change
.rowAtEndPoint() // Row at the end point
.hasNumberOfColumns(6) // Assertion on the row at end point of the first change
.exists() // Another assertion on the same row
.value(1) // Value at index 1 of the row at end point of the first change
.isEqualTo("McGuiness") // Assertion on the value
.returnToRow() // Go back to the row
.returnToChange() // Go back to the change
.change() // Next change
.rowAtEndPoint() // Row at end point of this change
.hasValues(1,
"Hewson",
"Paul David",
"Bono Vox",
"1960-05-10",
1.75)
.column("surname") // Column with name is "surname" of the second change (note that returnToChange() is not mandatory)
.isModified() // Assertion on column
.hasValues("Bono",
"Bono Vox")
.column() // Next column
.isNotModified() // Assertion on the column
.valueAtEndPoint() // Value at end point in the column after "surname" ("birth") of the second change
.isEqualTo(DateValue.of(
1960, 5, 10))
.ofDeletion() // All the changes of deletion (note that the returnToXxxx() methods are not mandatory)
.change() // First change of these changes of deletion
.isOnTable("albums")
.hasPksValues(15)
.changeOnTableWithPks("members", 5) // Change with primary key 5 on "members" table
.isCreation()
.rowAtEndPoint() // Row at end point of change with primary key 5 on "members" table
.hasValues(5,
"McGuiness",
"Paul",
null,
"1951-06-17",
null)
;

这是github上官方的例子。如果你可以完全掌控你们的数据库,使用changes做断言不失为一个选择。下面再看一个例子

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);
}

}

除了断言还可以像上面一样根据changes取出所有变化的数据信息并拼成sql。大家可以根据自己的需要取得这些信息。例如在自动化中hack一些信息。假如像我一样,拼出所有变化数据的sql。这样在某些特定的case,例如会影响其他case运行的一些场景。在case运行后自动拼出sql把数据恢复回去。 或者在有些自动化中你是获取不到一些信息的情况,例如后台发生了一些操作,而你并不知道或者不容易获取这些操作信息。就可以使用changes把这些信息都取出来,甚至根据需要hack一些东西。有兴趣的同学可以翻我之前写的监控式数据管理的帖子,看看我是怎么用changes做数据恢复的

Table

一直没说table的事。给个例子把,我几乎不怎么用。

Source source = new Source("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1", "user", "password");
Table table = new Table(source, "members");

assertThat(table).column("name")
.hasValues("Hewson", "Evans", "Clayton", "Mullen");

Source

在assertJ-db中,无论是changes,request还是table等等。初始化的时候都是要传递一个Source作为参数的。其实就是你要传递一个数据的datasouce给assertJ-db。 其实在assertJ-db中内置了一个source,你可以看到如下的调用

Source source = new Source("jdbc:mysql://172.27.1.219:3306/dango?useUnicode=yes&amp;characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull", "write", "!@#$1234%^&*5678ABCDabcd");
Changes changes = new Changes(source);

但是~注意了,但是assertJ-db内置的这种方式慢的要死。强烈不建议使用。 我是使用的mybatis的datasource搞的,因为我有些数据库操作是用mybatis搞的。

总结

好了,assertJ-db 科普帖到此结束。

* 注:本文来自网络投稿,不代表本站立场,如若侵犯版权,请及时知会删除