【问题标题】:How to cover private static final class members by unit test exp serialVersionUID如何通过单元测试 exp serialVersionUID 覆盖私有静态最终类成员
【发布时间】:2020-04-30 14:39:02
【问题描述】:

我正在添加单元测试以涵盖遗留的 java 项目(我无法更改),

我知道如何对这个类进行单元测试和覆盖,我通过在下面添加单元测试来做到这一点。

public class Thing implements Serializable {

    private static final long serialVersionUID = -1463850548670691860L;

    private String id;
}

举个例子:

class ThingTest {

    @Test
    void testShouldNotChangeSerialVersionUID() throws NoSuchFieldException {
        final Field serialVersionUID = Thing.class.getDeclaredField("serialVersionUID");
        serialVersionUID.setAccessible(true);

        assertEquals(-1463850548670691860L, getField(serialVersionUID, new Thing()));
    }
}

但 sonar 和 jacoco 将 serialVersionUID 显示为未涵盖。这是声音。

您能否提出一个解决方案,通过覆盖该领域来使声纳满意。

是的,我知道,我不应该遵循工具,而是遵循实践。

其他约束,我无法更改 serialVersionUID 的可见性。

谢谢

【问题讨论】:

  • 抱歉,这只是一个好奇:覆盖 serialVersionUID 有什么用处?
  • 为该字段添加测试的作用是强制想要修改值的人也更改测试,因此阅读一条消息向他解释为什么需要三思而后行这样做,例如向后兼容
  • 确保serialVersionUID 未被修改不会阻止用户以破坏序列化的方式修改类。如果您真的关心这一点,我建议将对象的序列化形式保存到资源文件中,将其添加到您的 src/test/resources 目录中,并编写一个测试以确保资源文件可以正确反序列化。
  • 我同意你的观点,可以通过反射来做到这一点,我也对你提到的那个特定情况进行了单元测试。
  • 你能用get方法包装它吗?

标签: java junit sonarqube code-coverage jacoco


【解决方案1】:

如果在您的 JUnit 中扩展 ObjectInputString,您可以在反序列化您序列化的对象时读取 serialVersionUID:

     private class MyObjectInputStream extends ObjectInputStream {

        public long uid;

        public MyObjectInputStream(InputStream in) throws IOException {
            super(in);
        }

        @Override
        protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException {
            ObjectStreamClass readClassDescriptor = super.readClassDescriptor(); //To change body of generated methods, choose Tools | Templates.
            uid = readClassDescriptor.getSerialVersionUID();
            return readClassDescriptor;
        }



    }

    @Test
    public void testSomeMethod() throws IOException, ClassNotFoundException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(byteArrayOutputStream);
        oos.writeObject(new Thing());
        MyObjectInputStream myObjectInputStream = new MyObjectInputStream(new ByteArrayInputStream(byteArrayOutputStream.toByteArray()));
        myObjectInputStream.readObject();
        assertEquals(-1463850548670691860L, myObjectInputStream.uid);
    }

【讨论】:

    【解决方案2】:

    您的测试覆盖率系统说“在您的单元测试期间没有人访问过这个字段”。这就是红条的意思。因此,要让它消失,您需要运行访问该字段的代码。使用反射访问字段来增加单元测试覆盖率是无稽之谈。尽管它可能会将字段变为绿色,但实际上并没有以有意义的方式使用该字段。

    这样做的正确方法是以使用该字段的方式练习测试类。在序列化/反序列化期间使用 serialVersionUID 来确定正在读取的字节是否与您尝试实例化的对象匹配。

    这是一个简单的示例,涵盖了您想要的内容。我在最后添加了一个相等测试,因为当您担心序列化/反序列化过程时,这真的应该测试。

    package org.does.not.exist;
    
    import org.junit.jupiter.api.Test;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.io.Serializable;
    
    import static org.assertj.core.api.Assertions.assertThat;
    
    class Foo implements Serializable {
         private static final long serialVersionUID = 42L;
    
        public Foo(String value) {
            this.value = value;
        }
    
        private String value;
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Foo)) return false;
    
            Foo foo = (Foo) o;
    
            return value != null ? value.equals(foo.value) : foo.value == null;
        }
    
        @Override
        public int hashCode() {
            return value != null ? value.hashCode() : 0;
        }
    }
    
    class StackoverflowTest {
    
        @Test
        void test() throws Exception {
            Foo foo = new Foo("a string!");
    
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream out = new ObjectOutputStream(bos);
            out.writeObject(foo);
    
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream in = new ObjectInputStream(bis);
            Foo copied = (Foo) in.readObject();
    
            assertThat(foo).isEqualTo(copied);
        }
    }
    

    按照上面的方法做,但是用你自己的类替换 Foo,你应该得到覆盖。

    【讨论】:

    • 这不测试向后兼容性
    • @Renato 正确,它没有。这只是证明您可以使用 Java 语言本机序列化过程来写出和读入对象的最小有用测试,这将为您提供本文所要求的愚蠢的 100% 测试覆盖率,同时实际上是一个有意义的测试时间。
    • 恕我直言不是正确答案,如果未指定 SerialVersionUID,这也将起作用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-19
    • 2014-09-02
    • 1970-01-01
    • 2016-01-19
    • 2011-02-24
    • 1970-01-01
    相关资源
    最近更新 更多