通过反射准备单元测试数据

(edit by king)在编写单元测试的时候,一般提倡将测试数据和测试代码分离。这样做的好处在于, 测试数据的管理方便,增加和减少测试数据都会相对方便一点。

通常,我们可以模仿junit提供的参数化这样的方法,将测试数据进行分类,变成多个test case。 其中可能会遇到的最大麻烦就是数据准备的格式,和如何通过数据文件,生成测试需要的数据。

我尝试使用xml方式来定义数据,xml的最大好处,就是能够通过标签和属性,自定义各种java的数据类型。 包括基本数据类型(如int, double, boolean等),数组类型,java bean等。 这里就介绍下这些数据类型的创建方法。

java提供了强大的反射机制,能够在运行时根据名称创建需要的对象。这里所有的对象创建都需要用到。 首先,对于基本数据类型,可以发现,通过int.class可以创建int的Class对象,但是却没有办法通过 Class.forName("int")来创建这个Class对象。所以,对于创建这种基本数据类型,我是通过对名称的判断, 创建对应的Class对象的。创建代码如下:

[cc lang='java'  escaped="true"  tab_size="4"]

private Class<?> getClassName(String valueType) throws ClassNotFoundException {

if ("boolean".equals(valueType)) {

return boolean.class;

} else if ("char".equals(valueType)) {

return char.class;

} else if ("int".equals(valueType)) {

return int.class;

} else if ("long".equals(valueType)) {

return long.class;

} else if ("short".equals(valueType)) {

return short.class;

} else if ("byte".equals(valueType)) {

return byte.class;

} else if ("float".equals(valueType)) {

return float.class;

} else if ("double".equals(valueType)) {

return double.class;

} else {

return Class.forName(valueType);

}

}

[/cc]

用前面的代码,能够根据名字创建出对象的Class对象,再通过apache的ConvertUtils, 将Class对象和通过数据文件获取的对象值(String类型),这样就能够创建基本数据类型、数组 和java对象的创建。

ConvertUtils帮我们实现了基本数据类型和它们数组,BeanUtils帮我们创建了需要的java bean。 但是,我们在测试异常数据的时候,往往还需要准备一类特殊的对象——异常。

为了能够不增加标签,我们仍然采用了原来的创建数据的value标签。这样,就需要在原来创建普通对象 的时候,对异常类型进行判断。根据原来的源码,是通过ConvertUtils.convert这个静态方法, 将文本内容和Class对象转换为制定对象的实例。通过对ConvertUtils代码的查看,它内部仅仅对 部分类型,有现成的实现,对于没有注册的类型,只会返回一个String对象。

因此,最方便的扩展方法,就是当Class对象是Exception的扩展类时,向ConvertUtils里面注册一个 转换对象。部分代码如下:

[cc lang='java' ]
if(Exception.class.isAssignableFrom(valueTypeClass)) {
ConvertUtils.register(new ExceptionConverter(), valueTypeClass);
}
[/cc]
ExceptionConverter的实现非常简单,如果有内容,则作为异常对象的消息,在构造时传入 (这个应该用处不大,测试的时候,一般只会判断是否有期望的异常抛出)。具体的实现:
[cc lang='java' ]

public class ExceptionConverter extends AbstractConverter {

@SuppressWarnings("unchecked")

@Override

protected Object convertToType(Class type, Object value) throws Throwable {

if (value instanceof String) {

try {

Constructor c = type.getConstructor(String.class);

return c.newInstance(value);

} catch (Exception e) {

return type.newInstance();

}

}

return type.newInstance();

}

@SuppressWarnings("unchecked")

@Override

protected Class getDefaultType() {

return Exception.class;

}

}

[/cc]
在使用过程,还发现了另外一个问题,如果在xml里面采用java的转义字符,digester会自动将 代表转义开始的反斜杠("\")进行转义,导致转义字符失效。因为特殊字符也是测试过程中非常用要的 用例,对于这种情况是必须要解决的。

找了半天,用apache commons库里面的StringEscapeUtils进行反转义,来解决这个问题。 用起来非常方便:

[cc lang='java' ]
if(valueObject instanceof String) {

valueObject = StringEscapeUtils.unescapeJava((String) valueObject);

}

[/cc]

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据