繁体   English   中英

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

[英]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)规范为类定义。 流中的项目顺序如下:

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM