Random Java objects for testing

As a Java developer you should test the code you’re writing. In a perfect world every line of code should be covered by a test scenario. But writing tests takes time and doesn’t add functionality to your product. To make test-writing as convenient as possible many testing frameworks like JUnit have emerged over time. When you test software or a particular piece of logic you usually follow same pattern: you prepare some input, run it through whatever algorithm you’ve crafted, and compare to the expected output. Or only feed the input into your machinery asserting that nothing goes wrong.

Preparing the input is tedious, especially for classes that have a lot of fields. In today’s post I will try to give an ansatz for systematically and repeatedly inflating an input object with random data. These random objects can be feed into your program and you can grab the popcorn and watch.

Now let’s get a little more concrete an give a class (with the imaginative name MyClass) that has a couple of primitive fields and a field with non-trivial substructure of type MyInnerClass. For each of them I’ve generated getters and setters (any decent IDE will do that for you). We have a default constructor that fills the fields with some values.

package pls.gooby;

public class MyClass {

   enum MyEnum {
      FIRST_ENUMCONSTANT,
      SECOND_ENUMCONSTANT,
      THIRD_ENUMCONSTANT
   }

   class MyInnnerClass {

      private Long innerLong;
      private MyEnum InnerEnum;

      MyInnnerClass() {
         innerLong = 10001L;
      }

      public Long getInnerLong() {
         return innerLong;
      }

      public void setInnerLong(Long innerLong) {
         this.innerLong = innerLong;
      }

      public MyEnum getInnerEnum() {
         return InnerEnum;
      }

      public void setInnerEnum(MyEnum innerEnum) {
         InnerEnum = innerEnum;
      }

      @Override
      public String toString() {
         return "MyInnnerClass{" +
               "innerLong=" + innerLong +
               ", InnerEnum=" + InnerEnum +
               '}';
      }
   }

   private String string;
   private int i;
   private float f;
   private MyInnnerClass myInnerObject;

   MyClass() {
      myInnerObject = new MyInnnerClass();
      string = "initialString";
      i = -5;
      f = 3.14F;
   }

   public String getString() {
      return string;
   }

   public void setString(String string) {
      this.string = string;
   }

   public int getI() {
      return i;
   }

   public void setI(int i) {
      this.i = i;
   }

   public float getF() {
      return f;
   }

   public void setF(float f) {
      this.f = f;
   }

   public MyInnnerClass getMyInnerObject() {
      return myInnerObject;
   }

   public void setMyInnerObject(MyInnnerClass myInnerObject) {
      this.myInnerObject = myInnerObject;
   }

   @Override
   public String toString() {
      return "MyClass{" +
            "string='" + string + '\'' +
            ", i=" + i +
            ", f=" + f +
            ", myInnerObject=" + myInnerObject +
            '}';
   }
}

Now you want to feed an instance of MyClass into your program. And maybe slight variations of that instance. And maybe also some crazy variations of it. Now here is the idea: we set the field variables at random and we do it in such a way that works on any field that has a getter and setter in any class. The magic that helps out here is java reflections.

Here is how I did it:

package pls.gooby;

import org.junit.Before;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

public class TestWithReflections {

   private MyClass myObject;
   private Random random;

   @Before
   public void setUp() {
      myObject = new MyClass();
      random = new Random();
   }

   @Test
   public void test() {
      for (int i = 0; i < 1000; i++) {
         modifyOneRandomMember(myObject);
         System.out.println(myObject);
         /**
          * Here goes your code to be tested and
          * bombarded with random objects
          */
      }
   }

   private boolean modifyOneRandomMember(Object o) {
      if (o == null) {
         return false;
      }
      List<Field> fields = Arrays.asList(o.getClass().getDeclaredFields());
      List<Field> fieldsWithGettersAndSetters = keepOnlyGetablesAndSetables(
            fields, o.getClass());
      if (fieldsWithGettersAndSetters.isEmpty()) {
         return false;
      }
      int randomInt = random.nextInt(fieldsWithGettersAndSetters.size());
      Field candidate = fields.get(randomInt);
      assert candidate != null;
      Method getter = getGetter(candidate, o.getClass());
      Method setter = getSetter(candidate, o.getClass());
      assert getter != null;
      assert setter != null;

      if (canBeManipulated(candidate)) {
         try {
            manipulateField(candidate, o, getter, setter);
         } catch (InvocationTargetException | IllegalAccessException e) {
            return false;
         }
      } else {
         try {
            return modifyOneRandomMember(getter.invoke(o));
         } catch (IllegalAccessException | InvocationTargetException e) {
            return false;
         }
      }
      return true;
   }

   private void manipulateField(Field candidate, Object o, Method getter,
         Method setter)
         throws InvocationTargetException, IllegalAccessException {
      Object newValue;
      Object oldValue = getter.invoke(o);
      Class<?> type = candidate.getType();
      boolean primitive = type.isPrimitive();
      boolean isWrapper = isWrapper(type);
      if (primitive || isWrapper) {
         if (type.equals(long.class) || type.equals(Long.class)) {
            setter.invoke(o, random.nextLong());
         } else if (type.equals(int.class) || type.equals(Integer.class)) {
            setter.invoke(o, random.nextInt());
         } else if (type.equals(boolean.class) || type
               .equals(Boolean.class)) {
            setter.invoke(o, random.nextBoolean());
         } else if (type.equals(char.class) || type
               .equals(Character.class)) {
            setter.invoke(o, (char) random.nextInt());
         } else if (type.equals(byte.class) || type.equals(Byte.class)) {
            setter.invoke(o, (byte) random.nextInt());
         } else if (type.equals(short.class) || type.equals(Short.class)) {
            setter.invoke(o, (short) random.nextInt());
         } else if (type.equals(float.class) || type.equals(Float.class)) {
            setter.invoke(o, random.nextFloat());
         } else if (type.equals(double.class) || type.equals(Double.class)) {
            setter.invoke(o, random.nextDouble());
         }
      } else if (type.equals(String.class)) {
         byte[] randBytes = new byte[10];
         random.nextBytes(randBytes);
         newValue = "" + new String(randBytes);
         setter.invoke(o, newValue);
      } else if (type.isEnum()) {
         newValue = nextEnum(oldValue, type);
         setter.invoke(o, newValue);
      }
   }

   private Object nextEnum(Object oldValue, Class<?> type)
         throws IllegalAccessException {
      if (type.getEnumConstants().length <= 0) {
         return null; //Shouldn't happen
      }
      return type.getEnumConstants()[random.nextInt(
            type.getEnumConstants().length)];
   }

   private boolean canBeManipulated(Field candidate) {
      return candidate.getType().isPrimitive() || candidate.getType().isEnum()
            || candidate.getType().equals(String.class) || isWrapper(
            candidate.getType());
   }

   private boolean isWrapper(Class<?> type) {
      return type.equals(Long.class) || type.equals(Integer.class)
            || type.equals(Boolean.class) || type.equals(Character.class)
            || type.equals(Byte.class) || type.equals(Short.class)
            || type.equals(Float.class) || type.equals(Double.class);
   }

   private static List<Field> keepOnlyGetablesAndSetables(List<Field> fields,
         Class<?> clazz) {
      List<Field> fieldsWithGettersAndSetters = new ArrayList<>();
      for (Field f : fields) {
         boolean hasGetter = getGetter(f, clazz) != null;
         boolean hasSetter = getSetter(f, clazz) != null;
         if (hasGetter && hasSetter) {
            fieldsWithGettersAndSetters.add(f);
         }
      }
      return fieldsWithGettersAndSetters;
   }

   public static Method getGetter(Field field, Class<?> clazz) {
      String getterName =
            "get" + field.getName().substring(0, 1).toUpperCase() + field
                  .getName().substring(1);
      try {
         return clazz.getDeclaredMethod(getterName);
      } catch (NoSuchMethodException e) {
         return null;
      }
   }

   public static Method getSetter(Field field, Class<?> clazz) {
      String getterName =
            "set" + field.getName().substring(0, 1).toUpperCase() + field
                  .getName().substring(1);
      try {
         return clazz.getDeclaredMethod(getterName, field.getType());
      } catch (NoSuchMethodException e) {
         return null;
      }
   }
}

Starting from some initial object myObject we call modifyOneRandomMember which searches through the members (with getters and setters) of myObject, selecting one at random and tries to fiddle around with it if possible, i.e. when encountering a “primitive” type like int, float, enum, String … If a non-primitive type was selected we recursively dive into that one, trying to do the job one layer deeper.

Running the test test() really outputs a bunch of funny objects:

MyClass{string='�N�[��Q٥', i=423107031, f=0.27292645, myInnerObject=MyInnnerClass{innerLong=-289882041982844357, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=423107031, f=0.79552823, myInnerObject=MyInnnerClass{innerLong=-289882041982844357, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=423107031, f=0.79552823, myInnerObject=MyInnnerClass{innerLong=-6130955001816810825, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=1371882886, f=0.79552823, myInnerObject=MyInnnerClass{innerLong=-6130955001816810825, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=1371882886, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=-6130955001816810825, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=-6130955001816810825, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='�N�[��Q٥', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=2736096219258417879, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='(�Iʌ�l5�', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=2736096219258417879, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='(�Iʌ�l5�', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=2736096219258417879, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='���N\��א', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=2736096219258417879, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='���N\��א', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=3936019081289932442, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='���N\��א', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=-8097030520172404526, InnerEnum=FIRST_ENUMCONSTANT}}
MyClass{string='���N\��א', i=-555770979, f=0.83158857, myInnerObject=MyInnnerClass{innerLong=-8097030520172404526, InnerEnum=THIRD_ENUMCONSTANT}}

The nice thing is that one can call modifyOneRandomMember on any object. The bad thing (the educated reader may already have spotted it) is that my implementation sucks in that member variables are in fact not selected for a modification with equal probability. I leave it up to you to fix this issue.

In a particular real-life application one might have to modify or better yet generalize the above algorithm. Maybe your variables need to underlie certain constraints (like all ints need to be greater than zero). Maybe your chain of objects needs to be “more random”, maybe all members should be randomized at each step. Maybe your chain of objects should be “less random” or concentrated around some “typical input”. The use of more sophisticated probability distributions comes to my mind. And then you can tell your Boss that you have tested your software using sophisticated Markov-Chain-Hybrid-Monte-Carlo-Metropolis-Hastings methods 😉

Advertisements

About goobypl5

pizza baker, autodidact, particle physicist
This entry was posted in Programming and tagged , , , , , . Bookmark the permalink.

Share your thoughts

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s