[英]How can I write a unit test to ensure a class definition is unmodified?
我们有一个使用Java序列化序列化的类。 此类很少应修改。 我想编写一个执行此规则的单元测试。 修改此字段后,意味着需要进行很多更改,并且版本会撞到相关的但又独立的应用程序,这些应用程序使用此对象的Java序列化调用我们的应用程序。
一种想法是仅使用自动生成的serialVersionUID。 问题在于它可以根据JVM的更改进行修改(这很烦人)。
关于如何处理此问题还有其他想法吗?
值得庆幸的是,班级非常平坦。 换句话说,该类没有复杂的子类。 它只是简单的Java对象(例如字符串,整数和数组)的集合。
最简单的选择是使用反射API获取类接口,非静态和非瞬态字段,构造函数和方法的列表,并断言它们没有更改。
当然,这意味着您必须对受此序列化影响的每个类都执行此操作(例如,如果您的根类具有其他对象的集合)。
作为参考,可以在Java序列化规范中找到SVUID算法:
serialVersionUID
使用反映类定义的字节流的签名来计算。 美国国家标准技术研究院(NIST)的安全哈希算法(SHA-1)用于计算流的签名。 前两个32位数量用于形成64位哈希。java.lang.DataOutputStream
用于将原始数据类型转换为字节序列。 输入到流的值由Java虚拟机(VM)规范为类定义。 流中的项目顺序如下:
- 使用UTF编码编写的类名称。
- 类修饰符写为32位整数。
- 每个接口的名称按使用UTF编码编写的名称排序。
- 对于按字段名称排序的类的每个字段(
private static
字段和private transient
字段除外):
- UTF编码的字段名称。
- 以32位整数形式编写的字段的修饰符。
- UTF编码中的字段的描述符
- 如果存在类初始值设定项,则写出以下内容:
- 方法的名称
<clinit>
,采用UTF编码。- 方法的修饰符
java.lang.reflect.Modifier.STATIC
,以32位整数形式编写。- 方法
()V
的描述符,采用UTF编码。- 对于每个按方法名称和签名排序的非私有构造函数:
- 方法的名称
<init>
,采用UTF编码。- 方法的修饰符,写为32位整数。
- UTF编码中方法的描述符。
- 对于按方法名称和签名排序的每个非私有方法:
- UTF编码中方法的名称。
- 方法的修饰符,写为32位整数。
- UTF编码中方法的描述符。
- SHA-1算法在
DataOutputStream
产生的字节流上执行,并产生五个32位值sha[0..4]
。哈希值由SHA-1消息摘要的第一和第二个32位值组成。 如果消息摘要的结果(五个32位字
H0 H1 H2 H3 H4
)位于五个名为sha的int
值的数组中,则哈希值的计算方式如下:
long hash = ((sha[0] >>> 24) & 0xFF) | ((sha[0] >>> 16) & 0xFF) << 8 | ((sha[0] >>> 8) & 0xFF) << 16 | ((sha[0] >>> 0) & 0xFF) << 24 | ((sha[1] >>> 24) & 0xFF) << 32 | ((sha[1] >>> 16) & 0xFF) << 40 | ((sha[1] >>> 8) & 0xFF) << 48 | ((sha[1] >>> 0) & 0xFF) << 56;
这是使用ASM 5.0框架中的SerialVersionUIDAdder重新计算SVUID的代码:
SerialVersionUIDAdder svuidv = new SerialVersionUIDAdder(Opcodes.ASM5, null) {
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
if ("serialVersionUID".equals(name)) {
return null;
}
return super.visitField(access, name, desc, signature, value);
}
protected void addSVUID(long svuid) {
if(svuid!=expectedsvid) {
throw new AssertionError("Serialization issue!");
}
}
};
InputStream is = AA.class.getResourceAsStream("/" + AA.class.getName().replace('.', '/') + ".class");
ClassReader cr = new ClassReader(is);
cr.accept(svuidv, ClassReader.SKIP_CODE);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.