测试类是否可序列化

问题描述 投票:1回答:4

我需要做的是进行单元测试,检查给定的类MyClass是否可序列化。但我不仅可以检查该类是否实现了Serializable:类的所有属性(以及属性的属性等)都必须实现Serializable。

此外,创建MyClass实例并尝试序列化和反序列化的解决方案并不令人满意:如果有人添加了属性但未更新单元测试,则单元测试将不会测试新属性。

一种解决方案可能是递归地使用反射来测试所有属性的类是否实现Serializable,但这看起来非常重。

是不是有更好的解决方案?

谢谢。

java unit-testing serialization
4个回答
3
投票

这是一个老问题,但我想我会添加我的2c。

即使您遍历整个对象,也无法保证对象实现可序列化。如果您有一个抽象的成员变量或者是一个接口,则无法说明您最终存储的对象是否将被序列化。如果你知道自己在做什么,它会被序列化。

This answer provides a solution by populating your objects with random data,但这可能是矫枉过正。

我已经创建了一个类,它通过忽略接口,java.lang.Object和抽象类的结构来进行递归。

package au.com.tt.util.test;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public final class SerializableChecker
{
    public static class SerializationFailure
    {
        private final String mContainingClass;
        private final String mMemberName;

        public SerializationFailure(String inNonSerializableClass, String inMemberName)
        {
            mContainingClass = inNonSerializableClass;
            mMemberName = inMemberName;
        }

        public String getContainingClass()
        {
            return mContainingClass;
        }

        public String getMemberName()
        {
            return mMemberName;
        }

        public String getBadMemberString()
        {
            if (mMemberName == null)
                return mContainingClass;
            return mContainingClass + "." + mMemberName;
        }

        @Override
        public String toString()
        {
            return "SerializationFailure [mNonSerializableClass=" + mContainingClass + ", mMemberName=" + mMemberName + "]";
        }
    }

    private static class SerializationCheckerData
    {
        private Set<Class<?>> mSerializableClasses;

        SerializationCheckerData()
        {
            mSerializableClasses = new HashSet<Class<?>>();
        }

        boolean isAlreadyChecked(Class<?> inClass)
        {
            return mSerializableClasses.contains(inClass);
        }

        void addSerializableClass(Class<?> inClass)
        {
            mSerializableClasses.add(inClass);
        }
    }

    private SerializableChecker()
    { }

    public static SerializationFailure isFullySerializable(Class<?> inClass)
    {
        if (!isSerializable(inClass))
            return new SerializationFailure(inClass.getName(), null);

        return isFullySerializable(inClass, new SerializationCheckerData());
    }

    private static SerializationFailure isFullySerializable(Class<?> inClass, SerializationCheckerData inSerializationCheckerData)
    {
        for (Field field : declaredFields(inClass))
        {
            Class<?> fieldDeclaringClass = field.getType();

            if (field.getType() == Object.class)
                continue;

            if (Modifier.isStatic(field.getModifiers()))
                continue;

            if (field.isSynthetic())
                continue;

            if (fieldDeclaringClass.isInterface() || fieldDeclaringClass.isPrimitive())
                continue;

            if (Modifier.isAbstract(field.getType().getModifiers()))
                continue;

            if (inSerializationCheckerData.isAlreadyChecked(fieldDeclaringClass))
                continue;

            if (isSerializable(fieldDeclaringClass))
            {
                inSerializationCheckerData.addSerializableClass(inClass);

                SerializationFailure failure = isFullySerializable(field.getType(), inSerializationCheckerData);
                if (failure != null)
                    return failure;
                else
                    continue;
            }

            if (Modifier.isTransient(field.getModifiers()))
                continue;

            return new SerializationFailure(field.getDeclaringClass().getName(), field.getName());
        }
        return null;
    }

    private static boolean isSerializable(Class<?> inClass)
    {
        Set<Class<?>> interfaces = getInterfaces(inClass);
        if (interfaces == null)
            return false;
        boolean isSerializable = interfaces.contains(Serializable.class);
        if (isSerializable)
            return true;

        for (Class<?> classInterface : interfaces)
        {
            if (isSerializable(classInterface))
                return true;
        }

        if (inClass.getSuperclass() != null && isSerializable(inClass.getSuperclass()))
            return true;

        return false;
    }

    private static Set<Class<?>> getInterfaces(Class<?> inFieldDeclaringClass)
    {
        return new HashSet<Class<?>>(Arrays.asList(inFieldDeclaringClass.getInterfaces()));
    }

    private static List<Field> declaredFields(Class<?> inClass)
    {
        List<Field> fields = new ArrayList<Field>(Arrays.asList(inClass.getDeclaredFields()));

        Class<?> parentClasses = inClass.getSuperclass();

        if (parentClasses == null)
            return fields;
        fields.addAll(declaredFields(parentClasses));

        return fields;
        }
    }

1
投票

作为一般检查,您可以使用Apache Commons中的SerializationUtils,如下所示:

byte [] data = SerializationUtils.serialize(obj);
Object objNew = SerializationUtils.deserialize(data);

但它不保证该类的其他实例将正确序列化;对于复杂对象,存在特定成员根据其成员不可序列化的风险。


0
投票

我认为序列化和反序列化实例是可以的。如果更改了类,则单元测试仍然有效,您需要比较原始对象和反序列化对象


0
投票

递归地使用反射并不完美,但可以部分地完成工作。

为此,您可以重用像this这样的实用程序类。

用法如下所示:

public class SerializationTests {

    @Test
    public void ensure_MyClass_is_serializable() {
        assertIsSerializable(MyClass.class);
    }

    @Test
    public void ensure_MyComplexClass_is_serializable() {
        // We excludes "org.MyComplexClass.attributeName" 
        // because we can not be sure it is serializable in a
        // reliable way.
        assertIsSerializable(MyComplexClass.class, "org.MyComplexClass.attributeName");
    }

    private static void assertIsSerializable(Class<?> clazz, String... excludes) {
        Map<Object, String> results = SerializationUtil.isSerializable(clazz, excludes);

        if (!results.isEmpty()) {
            StringBuilder issues = new StringBuilder();
            for (String issue : results.values()) {
                issues.append("\n");
                issues.append(issue);
            }
            fail(issues.toString());
        }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.