如何编写单元测试以确保类定义未修改?

时间:2014-09-19 16:47:40

标签: java junit

我们有一个使用java序列化序列化的类。这个类应该很少被修改。我想编写一个强制执行此规则的单元测试。当这个字段被修改时,它意味着很多变化,并且版本颠簸到依赖但分离的应用程序,这些应用程序使用此对象的java序列化来调用我们的。

一种想法就是使用自动生成的serialVersionUID。这个问题是它可以根据JVM的变化进行修改(这很难搞清楚)。

关于如何处理此事的任何其他想法?

谢天谢地,班级非常平坦。或者,换句话说,该类没有复杂的子类。它只是简单的java对象的集合,如字符串,整数和数组。

1 个答案:

答案 0 :(得分:2)

最简单的选择是使用反射API获取类接口列表,非静态和非瞬态字段,构造函数和方法,并声明它们没有更改。

这当然意味着您必须为受此序列化影响的每个类执行此操作(例如,如果您的根类具有其他对象的集合)。

作为参考,可以在Java serialization spec

中找到SVUID算法
  

serialVersionUID是使用流的签名计算的   反映类定义的字节。国家研究所   使用标准和技术(NIST)安全散列算法(SHA-1)   计算流的签名。前两个32位   数量用于形成64位散列。一个   java.lang.DataOutputStream用于将原始数据类型转换为   一系列字节。输入到流的值由。定义   类的Java虚拟机(VM)规范。该   流中的项目序列如下:

     
      
  1. 使用UTF编码编写的类名。
  2.   
  3. 类修饰符写为32位整数。
  4.   
  5. 按名称使用UTF编码排序的每个接口的名称。
  6.   
  7. 对于按字段名称排序的类的每个字段(private staticprivate transient字段除外):   
        
    1. UTF编码字段的名称。
    2.   
    3. 字段的修饰符,写为32位整数。
    4.   
    5. UTF编码字段的描述符
    6.   
  8.   
  9. 如果存在类初始值设定项,请写出以下内容:   
        
    1. 方法的名称<clinit>,采用UTF编码。
    2.   
    3. 方法的修饰符java.lang.reflect.Modifier.STATIC,写为32位整数。
    4.   
    5. 方法的描述符()V,采用UTF编码。
    6.   
  10.   
  11. 对于按方法名称和签名排序的每个非私有构造函数:   
        
    1. 方法的名称<init>,采用UTF编码。
    2.   
    3. 该方法的修饰符写为32位整数。
    4.   
    5. UTF编码方法的描述符。
    6.   
  12.   
  13. 对于按方法名称和签名排序的每个非私有方法:   
        
    1. UTF编码方法的名称。
    2.   
    3. 该方法的修饰符写为32位整数。
    4.   
    5. UTF编码方法的描述符。
    6.   
  14.   
  15. SHA-1算法在DataOutputStream生成的字节流上执行,并生成五个32位值sha[0..4]
  16.   
  17. 哈希值是从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;

  18.   

以下是使用来自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);