单元测试java项目中所有类的可序列化

时间:2011-10-11 20:44:19

标签: java unit-testing serialization

我的java项目中有数千个类。其中一些实现了可序列化的接口。现在这是一个问题。有人可以进入一个类,添加既不是瞬态也不可序列化的新变量。代码编译得很好,但是流程会在运行时爆炸。

为了说明这个

class Foo implements Serializable {  .... // all good }

class Foo implements Serializable 
{  
    // OOps, executorService is not serializable.  It's not declared as transient either 

    private ExecutorService executorService = ..
}

我正在考虑编写一个单元测试,该测试将通过所有类并确保“真正的可序列化”。我已经阅读了一些关于连续特定对象的讨论。我理解这个过程但需要

1)创建一个对象  2)序列化然后
 3)反序列化。

是否有更有效和实用的方法。也许用反射。通过所有类,如果类具有可序列化,那么所有属性必须是可序列化的或具有临时关键字..

想法?

2 个答案:

答案 0 :(得分:2)

  

1)创建一个对象。 2)序列化然后3)反序列化。

此列表不完整;你还需要初始化。考虑一下这个例子:

class CanBeSerialized implements Serializable {
    private String a; // serializable
    private Thread t; // not serializable
}

class CannotBeSerialized implements Serializable {
    private String a;                // serializable
    private Thread t = new Thread(); // not serializable
}

您可以序列化和反序列化第一个,但第二个会得到NotSerializableException。为了进一步复杂化,如果使用接口,你永远无法判断一个类是否会通过序列化,因为它是流接口的的具体对象:

class PerhapsCanBeSerializedButYouNeverKnow implements Serializable {
    private Runnable r; // interface type - who knows?
}

前提是您可以保证以下您的课程所用的所有课程和类进行测试:

  • 默认构造函数,
  • 字段中没有接口类型,

然后您可以通过反射自动创建和初始化它们,然后测试序列化。但那是一个非常艰难的条件,不是吗?否则,正确的初始化归结为手动工作。

您可以以不同的方式使用反射:遍历要检查的Class个对象列表,为它们获取Field[],并验证它们是否是瞬态的({{1}或者,如果他们直接(Field.getModifiers())或间接(通过超级接口或类)实现Serializable。另外,请考虑要检查的深度,具体取决于序列化机制的工作深度。

正如Ryan正确指出的那样,如果代码足够邪恶,这种静态序列化检查会失败:

Field.getType().getInterfaces()

或者只是class SeeminglySerializable implements Serializable { // ... private void writeObject/readObject() { throw new NotSerializableException(); } } 被严重执行。要测试这类问题,您需要实际测试序列化过程,而不是它背后的代码。

答案 1 :(得分:1)

如果序列化是您应用的关键部分,那么请在测试中包含序列化。类似的东西:

@Test
public void aFooSerializesAndDeserializesCorrectly {
    Foo fooBeforeSerialization = new Foo();
    ReflectionUtils.randomlyPopulateFields(foo);
    Foo fooAfterSerialization = Serializer.serializeAndDeserialize(foo);
    assertThat(fooAfterSerialization, hasSameFieldValues(fooBeforeSerialization));
}

修改randomlyPopulateFields的简单实现:

public static void randomlyPopulateFields(final Object o) {
    ReflectionUtils.doWithFields(o.getClass(), new ReflectionUtils.FieldCallback() {
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, o, randomValueFor(field.getType()));
        }

        private Random r = new Random();
        private Object randomValueFor(Class<?> type) {
            if (type == String.class) {
                return String.valueOf(r.nextDouble());
            } else if (type == Boolean.class || type == Boolean.TYPE) {
                return r.nextBoolean();
            } else if (type == Byte.class || type == Byte.TYPE) {
                return (byte) r.nextInt();
            } else if (type == Short.class || type == Short.TYPE) {
                return (short) r.nextInt();
            } else if (type == Integer.class || type == Integer.TYPE) {
                return r.nextInt();
            } else if (type == Long.class || type == Long.TYPE) {
                return (long) r.nextInt();
            } else if (Number.class.isAssignableFrom(type) || type.isPrimitive()) {
                return Byte.valueOf("1234");
            } else if (Date.class.isAssignableFrom(type)) {
                return new Date(r.nextLong());
            } else {
                System.out.println("Sorry, I don't know how to generate values of type " + type);
                return null;
            }
        }
    });
}