views:

373

answers:

3

When you run a JUnit 4 ParameterizedTest with the eclipse testrunner, the graphical representation is rather dumb: for each test you have a node called [0], [1], etc. Is it possible give the tests [0], [1] etc. explicit names? Implementing a toString method for the tests does not seem to help.

(This is a follow-up question to JUnit test with dynamic number of tests.)

+1  A: 

I think there's nothing built in in jUnit 4 to do this.

I've implemented a solution. I've built my own Parameterized class based on the existing one:

public class MyParameterized extends TestClassRunner {
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public static @interface Parameters {
    }

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public static @interface Name {
    }

    public static Collection<Object[]> eachOne(Object... params) {
     List<Object[]> results = new ArrayList<Object[]>();
     for (Object param : params)
      results.add(new Object[] { param });
     return results;
    }

    // TODO: single-class this extension

    private static class TestClassRunnerForParameters extends TestClassMethodsRunner {
     private final Object[] fParameters;

     private final Class<?> fTestClass;

     private Object instance;

     private final int fParameterSetNumber;

     private final Constructor<?> fConstructor;

     private TestClassRunnerForParameters(Class<?> klass, Object[] parameters, int i) throws Exception {
      super(klass);
      fTestClass = klass;
      fParameters = parameters;
      fParameterSetNumber = i;
      fConstructor = getOnlyConstructor();
      instance = fConstructor.newInstance(fParameters);
     }

     @Override
     protected Object createTest() throws Exception {
      return instance;
     }

     @Override
     protected String getName() {
      String name = null;
      try {
       Method m = getNameMethod();
       if (m != null)
        name = (String) m.invoke(instance);
      } catch (Exception e) {
      }
      return String.format("[%s]", (name == null ? fParameterSetNumber : name));
     }

     @Override
     protected String testName(final Method method) {
      String name = null;
      try {
       Method m = getNameMethod();
       if (m != null)
        name = (String) m.invoke(instance);
      } catch (Exception e) {
      }
      return String.format("%s[%s]", method.getName(), (name == null ? fParameterSetNumber : name));
     }

     private Constructor<?> getOnlyConstructor() {
      Constructor<?>[] constructors = getTestClass().getConstructors();
      assertEquals(1, constructors.length);
      return constructors[0];
     }

     private Method getNameMethod() throws Exception {
      for (Method each : fTestClass.getMethods()) {
       if (Modifier.isPublic((each.getModifiers()))) {
        Annotation[] annotations = each.getAnnotations();
        for (Annotation annotation : annotations) {
         if (annotation.annotationType() == Name.class) {
          if (each.getReturnType().equals(String.class))
           return each;
          else
           throw new Exception("Name annotated method doesn't return an object of type String.");
         }
        }
       }
      }
      return null;
     }
    }

    // TODO: I think this now eagerly reads parameters, which was never the
    // point.

    public static class RunAllParameterMethods extends CompositeRunner {
     private final Class<?> fKlass;

     public RunAllParameterMethods(Class<?> klass) throws Exception {
      super(klass.getName());
      fKlass = klass;
      int i = 0;
      for (final Object each : getParametersList()) {
       if (each instanceof Object[])
        super.add(new TestClassRunnerForParameters(klass, (Object[]) each, i++));
       else
        throw new Exception(String.format("%s.%s() must return a Collection of arrays.", fKlass.getName(), getParametersMethod().getName()));
      }
     }

     private Collection<?> getParametersList() throws IllegalAccessException, InvocationTargetException, Exception {
      return (Collection<?>) getParametersMethod().invoke(null);
     }

     private Method getParametersMethod() throws Exception {
      for (Method each : fKlass.getMethods()) {
       if (Modifier.isStatic(each.getModifiers())) {
        Annotation[] annotations = each.getAnnotations();
        for (Annotation annotation : annotations) {
         if (annotation.annotationType() == Parameters.class)
          return each;
        }
       }
      }
      throw new Exception("No public static parameters method on class " + getName());
     }
    }

    public MyParameterized(final Class<?> klass) throws Exception {
     super(klass, new RunAllParameterMethods(klass));
    }

    @Override
    protected void validate(MethodValidator methodValidator) {
     methodValidator.validateStaticMethods();
     methodValidator.validateInstanceMethods();
    }

}

To be used like:

@RunWith(MyParameterized.class)
public class ParameterizedTest {
    private File file;
    public ParameterizedTest(File file) {
     this.file = file;
    }

    @Test
    public void test1() throws Exception {}

    @Test
    public void test2() throws Exception {}

    @Name
    public String getName() {
     return "coolFile:" + file.getName();
    }

    @Parameters
    public static Collection<Object[]> data() {
     // load the files as you want
     Object[] fileArg1 = new Object[] { new File("path1") };
     Object[] fileArg2 = new Object[] { new File("path2") };

     Collection<Object[]> data = new ArrayList<Object[]>();
     data.add(fileArg1);
     data.add(fileArg2);
     return data;
    }
}

This implies that I instantiate the test class earlier. I hope this won't cause any errors ... I guess I should test the tests :)

bruno conde
A: 

There's no hint that this feature is or will be implemented. I would request this feature because it's nice to have.

furtelwart
A: 

A code-less though not that comfortable solution is to pass enough context information to identify the test in assert messages. You will still see just testXY[0] failed but the detailed message tells you which one was that.

assertEquals("Not the expected decission for the senator " + this.currentSenatorName + " and the law " + this.votedLaw, 
expectedVote, actualVote);
Jakub Holý