ParameterizedTest with a name in Eclipse Testrunner

前端 未结 5 569
小鲜肉
小鲜肉 2021-02-06 04:04

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], [

相关标签:
5条回答
  • 2021-02-06 04:13

    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 :)

    0 讨论(0)
  • 2021-02-06 04:20

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

    0 讨论(0)
  • 2021-02-06 04:23

    JUnit4 now allows specifying a name attribute to the Parameterized annotation, such that you can specify a naming pattern from the index and toString methods of the arguments. E.g.:

    @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 } });
    }
    
    0 讨论(0)
  • 2021-02-06 04:28

    If you use JUnitParams library (as I have described here), the parameterized tests will have their stringified parameters as their own default test names.

    Moreover, you can see in their samples, that JUnitParams also allows you to have a custom test name by using @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) { }
    
    0 讨论(0)
  • 2021-02-06 04:32

    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 decision for the senator " + this.currentSenatorName + " and the law " + this.votedLaw, 
    expectedVote, actualVote);
    
    0 讨论(0)
提交回复
热议问题