我的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)反序列化。
有没有更有效,更实用的方法。 也许要使用反射。 遍历所有类,如果类具有可序列化的属性,则所有属性必须可序列化或具有transient关键字。
有什么想法吗?
1) creating an object. 2) serializing and then 3) deserializing.
此列表不完整;您还需要初始化。考虑示例:
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[],并验证它们是否是瞬时的(Field.getModifiers())或它们是否实现了Serializable直接(Field.getType().getInterfaces())或间接(通过超级接口或类)。另外,请考虑要检查的深度,具体取决于序列化机制的工作深度。
正如Ryan正确指出的那样,如果代码足够邪恶,则此静态序列化检查将失败:
class SeeminglySerializable implements Serializable {
// ...
private void writeObject/readObject() {
throw new NotSerializableException();
}
}
或readObject()/writeObject()实施不当。要针对此类问题进行测试,您需要实际测试序列化过程,而不是其背后的代码。
您仍然没有考虑神奇的readObject和writeObject方法。如果这些被使用,则您列出的任何规则都将不适用。
@RyanStewart我想提一下,我的算法只是一个近似值,但是它让我感到困惑。谢谢!
如果序列化是应用程序的关键部分,请在测试中包括序列化。就像是:
@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;
}
}
});
}
您指的是什么ReflectionUtils?
没有特别的限制。我想我已经在某个地方的某个库中看到了这样的实用程序,但是我通常只是编写自己的。任何创建适当填充的对象的方法都可以使用。关键是,如果您要测试序列化,则需要序列化对象。
问题是,如果不提供有关如何初始化对象的元数据,就无法轻松实现这种方法。这就是为什么我发现您的答案具有误导性的原因,因为它欺骗了看似容易解决的问题。
添加了一个简单的实现。最糟糕的部分是必须分别处理所有数字类型。它不是很难。这也是获取填充对象进行测试的唯一可能方法。实际方法取决于您已经建立了什么样的测试基础架构。
大。现在,您清楚地显示出方法背后没有任何魔力,只有努力。