Unitils 使用指南
单元测试起码理论上应该简单和直观的,但是真实生活中的项目通常涉及很多方面,例如数据驱动和中间件技术的使用,例如EJB和hibernate.
Unitils 起初希望在测试中能找到一个比较实际的视角来解决问题。它开始有一套自己的指导原则并且有了一个开源的库包含一些工具,这些工具使遵循了这些原则的应用便的容易测试。
这个使用指南将给你展示一些例子,来掩饰Unitils可以对你的项目做什么。如果你想学习怎么配置Unitils 来快速上手,你可以读我们的cookbook.
- 断言工具
- Unitils 模块
- 数据库测试
- 自动测试数据维护
- 使用hibernate的程序测试
- 使用JPA的应用测试
- 使用Spring的应用的测试
- 使用Mock对象的应用的测试
- 对EasyMock的支持
1 断言工具
我们开始这个想到通过描述一些断言工具,他可以在Unitils核心模块系统之外独立使用,这这个文档的附件里会描述这些。不需要配置,只需要增加Unitils包和核心依赖到你项目的classpath既可以在你的单元测试里使用它了。
2 为断言使用反射
一个典型的单元测试通常包含对希望值和实际值的比较。Unitils提供断言工具抱住你做这件事情。让我们开始一个比较两个用户实例的例子,这个用户实例包含用户id,first 和last name.
public class User {
private long id;
private String first;
private String last;
public User(long id, String first, String last) {
this.id = id;
this.first = first;
this.last = last;
}
}
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1, user2);
你可能期望这个断言是成功的,因为两个实例包含合理相同的值,然而不是这种情况,因为User对象没有重写equals方法,检查两个User实例的相等其实是检查两个对象是否为同一个对象。换句话说上面的验证将会返回fail.
加入你实现了如下的相等方法:
public boolean equals(Object object) {
if (object instanceof User) {
return id == ((User) object).id;
}
return false;
}
这是一个合理的实现符合你的应用逻辑,通过两个用户实例是否有相同的idl来比较他们是否相等。然而这个方法对你的单元测试是没用的。测试是否两个对象相等便成了测试他们是否拥有相同的id了。
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "Jane", "Smith");
assertEquals(user1, user2);
这个断言将是成功的,但可能不是你想要的。最好的是避免使用equals()当你比较对象的时候(当然除了表达值的对象,如java.lang.String?)。你也可以比较每一个属性:
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertEquals(user1.getId(), user2.getId());
assertEquals(user1.getFirst(), user2.getFirst());
assertEquals(user1.getLast(), user2.getLast());
Unitils提供了更容易的方式来检查这些属性,就是通过映射。
使用 ReflectionAssert.assertReflectionEquals,上面的例子可以被改写成这样:
User user1 = new User(1, "John", "Doe");
User user2 = new User(1, "John", "Doe");
assertReflectionEquals(user1, user2);
这个断言通过反射来循环比两个对象的所有的属性值,对上面的例子,他会以此比较id值,first值和last值。
如果一个属性自己也是一个对象,它将递归的去比较这个对象的属性值。对collections,maps,arrays类型也是一样。他们的元素将被贯穿的递归的通过反射来比较。如果一个值是基本类型或者基本封装类型,他们将会通过值的是否相同来比较。结果是下面的断言将返回成功。
assertReflectionEquals(1, 1L);
List<Double> myList = new ArrayList<Double>();
myList.add(1.0);
myList.add(2.0);
assertReflectionEquals(Arrays.asList(1, 2), myList);
3 宽容的断言
由于可维护性的远影,为测试仅仅添加值的断言是重要的。让我用一个例子说明一下原因:假设你有一个测试计算一个账户余额,那么任何检查这个银行账户的姓名则是没有必要的。这只会增加你测试的复杂度,而且变的不易理解,更重要的是如果代码改动很容易造成不好的影响。如果你想你的测试代码容易保留下来,在重构和代码变动的时候,那么请尽量制作必要的断言。
为了帮助你写断言,我们提供了一些反射断言检查的级别,后面会详细介绍。
4 不重要的次序
To implement this behavior, the ReflectionAssert.assertReflectionEquals method can be configured to ignore ordering by supplying it theReflectionComparatorMode.LENIENT_ORDER comparator mode:
List<Integer> myList = Arrays.asList(3, 2, 1);
assertReflectionEquals(Arrays.asList(1, 2, 3), myList, LENIENT_ORDER);
第一种可以容忍的情况你可能会想到集合类型(collection or array)中元素的次序.当使用这些集合类型,你经常不会关系元素的次序。例如遍历所有银行账户的负账户数代码会按照一定顺序返回这个集合,但是顺序在实际处理中并不重要。
5 忽略默认值
第二种类型的容忍模式可以通过 ReflectionComparatorMode.IGNORE_DEFAULTS 来设定。
配置这个模式以后,java默认值,像对对象的null,以及0,false都会被忽略。换句话说,只有那些属性被属性(在你期望对象中有意指定的属性)在你的对比中。
下面有个列子,假设你有一个用户类(first name,last name,street 等字段),你只希望检查结果的用户对象中是否有特定的firstname 和 street 值,而忽略其他属性值。
User actualUser = new User("John", "Doe", new Address("First street", "12", "Brussels"));
User expectedUser = new User("John", null, new Address("First street", null, null));
assertReflectionEquals(expectedUser, actualUser, IGNORE_DEFAULTS);
在断言语句中,第一个对象中,如果属性被设置为null ,那么它将在比较中被忽略。但是第二个参数中的null 值,仍会被比较,是不会被忽略的。
assertReflectionEquals(null, anyObject, IGNORE_DEFAULTS); // Succeeds
assertReflectionEquals(anyObject, null, IGNORE_DEFAULTS); // Fails
6 可以容忍的数据
另一种宽容的策略是通过ReflectionComparatorMode.LENIENT_DATES来设置。这样断言语句会判断实例中的属性是否都被设置了或者是否都等于null,而不在乎具体日期是什么值。这个特性是比较有用在你想对对象属性做严格检查的时候(没有使用ReflectionComparatorMode.IGNORE_DEFAULTS),但是你对象的属性使用了当前日期或者时间。
Date actualDate = new Date(44444);
Date expectedDate = new Date();
assertReflectionEquals(expectedDate, actualDate, LENIENT_DATES);