2008-12-22 6 views
9

Lorsque vous exécutez un JUnit 4 ParameterizedTest avec l'Eclipse TestRunner, la représentation graphique est assez stupide: pour chaque test que vous avez un nœud appelé [0], [1], etc. Est-il possible faire passer les tests [0], [1], etc. noms explicites? L'implémentation d'une méthode toString pour les tests ne semble pas utile.ParameterizedTest avec un nom dans Eclipse TestRunner

(Ceci est une question de suivi à JUnit test with dynamic number of tests.)

Répondre

3

junit4 maintenant allows specifying a name attribute à l'annotation paramétrés, de sorte que vous pouvez spécifier un schéma de nommage des méthodes d'index et toString des arguments. .: par exemple

@Parameters(name = "{index}: fib({0})={1}") 
public static Iterable<Object[]> data() { 
    return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 }, 
      { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } }); 
} 

5

Je pense qu'il n'y a rien construit en dans JUnit 4 pour ce faire.

J'ai implémenté une solution. J'ai construit ma propre Parameterized classe basée sur l'existant:

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(); 
    } 

} 

A utiliser comme:

@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; 
    } 
} 

Cela implique que j'instancier la classe de test plus tôt. J'espère que cela ne causera pas d'erreurs ... Je suppose que je devrais tester les tests :)

+1

Pour votre information: Cette réponse est toujours correcte pour JUnit 4.0, mais heureusement l'ajout de noms personnalisés est possible dans JUnit 4.11. Voir http://stackoverflow.com/a/10143872/103814 – NobodyMan

0

Il n'y a aucune indication que cette fonctionnalité est ou sera implémentée. Je demanderais cette fonctionnalité parce que c'est bien d'avoir.

+1

Plus vrai maintenant. –

2

Une solution sans code mais pas aussi confortable consiste à transmettre suffisamment d'informations contextuelles pour identifier le test dans les messages d'assertion. Vous verrez toujours que testXY [0] a échoué mais le message détaillé vous indique lequel était.

assertEquals("Not the expected decision for the senator " + this.currentSenatorName + " and the law " + this.votedLaw, 
expectedVote, actualVote); 
1

Si vous utilisez JUnitParams library (comme je l'ai described here), les tests paramétrés auront leurs paramètres comme leurs propres de chaîne de caractères des noms de test par défaut.

De plus, you can see in their samples, que JUnitParams vous permet également d'avoir un nom de test personnalisé en utilisant @TestCaseName:

@Test 
@Parameters({ "1,1", "2,2", "3,6" }) 
@TestCaseName("factorial({0}) = {1}") 
public void custom_names_for_test_case(int argument, int result) { } 

@Test 
@Parameters({ "value1, value2", "value3, value4" }) 
@TestCaseName("[{index}] {method}: {params}") 
public void predefined_macro_for_test_case_name(String param1, String param2) { }