Unit Testing serializability for all classes in java project

北慕城南 提交于 2019-12-21 05:42:06

问题


I've thousands of classes in our java project. Some of them implements serializable interface. Now here's a problem. It's possible someone can go in a class, add new variable that is neither transient nor serializable. Code compiles fine however process would blow up at runtime.

To illustrate this

class Foo implements Serializable {  .... // all good }

class Foo implements Serializable 
{  
    // OOps, executorService is not serializable.  It's not declared as transient either 

    private ExecutorService executorService = ..
}

I'm thinking about writing a unit test that would go thru all classes and ensure "true serializability". I've read some discussions about serialing specific objects. i understand that process but it requires

1) creating an object.
2) serializing and then
3) deserializing.

Is there more efficient and practical approach. perhaps to use reflection. Go thru all classes, if class has serializable then all attributes must be serializable or have transient keyword..

Thoughts ?


回答1:


1) creating an object. 2) serializing and then 3) deserializing.

This list is not complete; you also need initialization. Consider the example:

class CanBeSerialized implements Serializable {
    private String a; // serializable
    private Thread t; // not serializable
}

class CannotBeSerialized implements Serializable {
    private String a;                // serializable
    private Thread t = new Thread(); // not serializable
}

You can serialize and deserialize the first one, but you'll get NotSerializableException on the second. To complicate the matter further, if interfaces are used, you can never tell if a class will pass serialization, as it's the concrete object of the class behind this interface that will be streamed:

class PerhapsCanBeSerializedButYouNeverKnow implements Serializable {
    private Runnable r; // interface type - who knows?
}

Provided that you could guarantee the following for all your classes and classes used by your classes to be tested:

  • default constructor exists,
  • no interface types in fields,

then you could automatically create and initialize them by reflection, and then test serialization. But that is a really hard condition, isn't it? Otherwise, proper initialization is down to manual work.

You could use reflection in a different way: iterating through a list of Class objects you want to check, getting the Field[] for them, and verifying if they're transient (Field.getModifiers()) or if they implement Serializable directly (Field.getType().getInterfaces()) or indirectly (via a super interface or class). Also, consider how deep you want to check, depending on how deep your serialization mechanism works.

As Ryan pointed out correctly, this static serialization check would fail if the code was evil enough:

class SeeminglySerializable implements Serializable {
    // ...
        private void writeObject/readObject() {
             throw new NotSerializableException();
        }
}

or just if readObject()/writeObject() were badly implemented. To test against this kind of problems, you need to actually test the serialization process, not the code behind it.




回答2:


If serialization is a key part of your app, then include the serialization in your tests. Something like:

@Test
public void aFooSerializesAndDeserializesCorrectly {
    Foo fooBeforeSerialization = new Foo();
    ReflectionUtils.randomlyPopulateFields(foo);
    Foo fooAfterSerialization = Serializer.serializeAndDeserialize(foo);
    assertThat(fooAfterSerialization, hasSameFieldValues(fooBeforeSerialization));
}

Edit: A trivial implementation of randomlyPopulateFields:

public static void randomlyPopulateFields(final Object o) {
    ReflectionUtils.doWithFields(o.getClass(), new ReflectionUtils.FieldCallback() {
        public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, o, randomValueFor(field.getType()));
        }

        private Random r = new Random();
        private Object randomValueFor(Class<?> type) {
            if (type == String.class) {
                return String.valueOf(r.nextDouble());
            } else if (type == Boolean.class || type == Boolean.TYPE) {
                return r.nextBoolean();
            } else if (type == Byte.class || type == Byte.TYPE) {
                return (byte) r.nextInt();
            } else if (type == Short.class || type == Short.TYPE) {
                return (short) r.nextInt();
            } else if (type == Integer.class || type == Integer.TYPE) {
                return r.nextInt();
            } else if (type == Long.class || type == Long.TYPE) {
                return (long) r.nextInt();
            } else if (Number.class.isAssignableFrom(type) || type.isPrimitive()) {
                return Byte.valueOf("1234");
            } else if (Date.class.isAssignableFrom(type)) {
                return new Date(r.nextLong());
            } else {
                System.out.println("Sorry, I don't know how to generate values of type " + type);
                return null;
            }
        }
    });
}


来源:https://stackoverflow.com/questions/7732259/unit-testing-serializability-for-all-classes-in-java-project

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!