Junit4: Run only a test of a class with Parameterized runner

Created on 11 Apr 2013  ·  35Comments  ·  Source: junit-team/junit4

When using the Parameterized runner there is no possibility to run only a test method of a test class.

For example when running in maven: mvn test -Dtest=TesClass#testName

No tests are run when using the Parameterized runner.

That would be great to have this feature, has for development is comon to the developers trying to run only the test they are working on.

feature parameterized

Most helpful comment

@diogoeag
Did you read this chapter in Surefire?
http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html#Multiple_Formats_in_One

Filtering parameterized tests works since of version 2.19. In your case it is -Dtest=TesClass#testName[*] instead of -Dtest=TesClass#testName.

All 35 comments

Fixing this would be great, we end up having to comment UTs in large test classes which is undesirable.

The Maven Surefire plugin allows to to specify globs (see http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html). Have you tried that?

Yes, that's how we would like to use, but it doesn't work. Are you saygin that this is a problem of the surefire plugin and not from junit?

@diogoeag I believe it's a problem with the Surefire plugin. You can verify that by writing a class with a main method that uses JUnitCore to run the tests of a class that used @Parameterized and have your main method apply a filter to select one of the tests

Hi Kevin,

Take a look at these two samples:

First test without parameterized:

package org;

import org.junit.Test;
import org.junit.internal.requests.FilterRequest;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.manipulation.Filter;

public class NoParameterTest {

    public static void main(String[] args) {
        new JUnitCore().run(FilterRequest.aClass(NoParameterTest.class).filterWith(new Filter() {
            @Override
            public boolean shouldRun(Description description) {
                System.out.println("Should run test: classname[" + description.getClassName() + "] method name[" + description.getMethodName() + "]");
                return description != null && description.getMethodName() != null && description.getMethodName().equals("test");
            }

            @Override
            public String describe() {
                return null;
            }
        }).getRunner());

    }

    @Test
    public void test() {
        System.out.println("Running Test: test");
    }

    @Test
    public void test2() {
        System.out.println("Running Test: test2");
    }
}

output

Should run test: classname[org.NoParameterTest] method name[test]
Should run test: classname[org.NoParameterTest] method name[test2]
Running Test: test

Second test with parameterized:

package org;

import org.junit.Test;
import org.junit.internal.requests.FilterRequest;
import org.junit.runner.Description;
import org.junit.runner.JUnitCore;
import org.junit.runner.RunWith;
import org.junit.runner.manipulation.Filter;
import org.junit.runners.Parameterized;

import java.util.Arrays;
import java.util.Collection;

@RunWith(Parameterized.class)
public class ParameterTest {

    public static void main(String[] args) {
        new JUnitCore().run(FilterRequest.aClass(ParameterTest.class).filterWith(new Filter() {
            @Override
            public boolean shouldRun(Description description) {
                System.out.println("Should run test: classname[" + description.getClassName() + "] method name[" + description.getMethodName() + "]");
                return description != null && description.getMethodName() != null && description.getMethodName().equals("test");
            }

            @Override
            public String describe() {
                return null;
            }
        }).getRunner());

    }


    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[]{"Paramter 1"}, new Object[]{"Parameter 2"});
    }

    private String localParameter;

    public ParameterTest(String parameter) {

        this.localParameter = parameter;
    }

    @Test
    public void test() {
        System.out.println("Running Test: test");
        System.out.println("Parameter: " + localParameter);
    }

    @Test
    public void test2() {
        System.out.println("Running Test: test2");
        System.out.println("Parameter: " + localParameter);
    }
}

output

Should run test: classname[[0]] method name[null]
Should run test: classname[[1]] method name[null]

The Description passed to the Filter by the Parameterized is not correct right?

@diogoeag Thanks for the code example. What version of JUnit did you try this on? I think we fixed a related issue in 4.12 but I am not sure.

@kcooney, I've tested with the current master 4.12-SNAPSHOT and also with 4.11. Both have the same problem.

Thanks. Fixing this would like require changes that are not backwards compatible. Setting milestone to 5.0

Just to clarify, fixing this would only need to go to BlockJUnit4ClassRunnerWithParameters and remove the override of the #testName method. That is replacing the default

    protected String testName(FrameworkMethod method) {
        return method.getName();
    }

by

    @Override
    protected String testName(FrameworkMethod method) {
        return method.getName() + getName();
    }

And the consequencies would be that the reporting of the tests would not have the parameter name in the testHeader (at least the tests that have failed were only these)?

@diogoeag but wouldn't we want the parameter name in the description?

I think the fixes would best be made on top of the Description builder we are working on for JUnit 5.0.

@kcooney Yes to be correct we should have the parameter name in the description, but the description should not affect the test name. I agree with you that the Description should have a test name that should be the correct name and the display name that should be used in user facing descriptions and include the parameter.

Do you have any estimates on the target date to 5.0 ?

I was looking to the usages of getDisplayName to submit a pull request to 5.0 with the fix, however it is used 23 times in the project (sources and tests) and I don't have the enough knowledge of JUnit source to make such change. Meanwhile I will make a fork for my personal usage with that intermediate fix for me. Having the parameter names is not critical, however allowing us to run single tests saves us many hours in many developers running tests in our source.

Thanks

If anyone needs to have access to this temporary fix: https://github.com/feedzai/junit/commit/be41d2980227d86915d9c8b6f800070a0ef1ff7a

@diogoeag
See this. Would it help in surefire 2.18 ?
https://github.com/apache/maven-surefire/pull/46

@Tibor17 I'm not sure. Because shoudlRun will use the description also. I don't know enough of the codebase to know if it will work. Only testing it. My sample code can be one of the tests for your fix also. Using parameterized tests has the problem of changing the test name.

@diogoeag
In surefire MethodFilter you should be able to specify condition something like this:

String filterBy = JunitVersion < 5.0 & @RunWith = Parameterized.class ? methodName + className : methodName

If you want to tolerate the JUnit issues, you can commit hotfix by yourself in Maven Surefire project.

@diogoeag we don't yet have a target date for JUnit 5.0, but it would likely be the release after 4.12. We are currently focusing on getting 4.12 out the door

@kcooney thanks. We will use our internal release meanwhile.

Hello, what about this issue? Is there a workaround to use Parameterized.class and have the possibility to run a single test using maven surefire? Or do we have to wait the 5.0 version for it?

@Fanch- there is no current fix. We thought would fix this on too of the ImmutableDescription work we were working on, but that effort was abandoned.

screen shot 2016-11-16 at 17 42 48

I think it would be ok to remove getName() from testName() because it's redundant anyway.

@junit-team/junit-committers What do you think?

As long as all Description instances are unique for a partameterized test.

We could set the unique ID in the description to make them unique. However, I'm wondering if/why they need to be unique in the whole subtree?

Yes, Description instances need to be unique. That's all that is passed to the listener to reference a test starting and ending. To build a tree like you showed and update it during the test run you need to know which test started.

Point taken. So, I think we should add a new factory method to Description that creates a test description with an explicit unique ID. We could then pass the current name as unique ID and use just the method's name as its display name. What do you think?

@marcphilipp there already is a method in Description for creating a test Description with a given unique ID: https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runner/Description.java#L109

I don't think that Description should _generate_ unique IDs. Couldn't Parameterized pass a deterministic value to Description.createTestDescription()?

there already is a method in Description for creating a test Description with a given unique ID: https://github.com/junit-team/junit4/blob/master/src/main/java/org/junit/runner/Description.java#L109

Yeah, but that one does not have a class parameter. So we need a different one.

I don't think that Description should _generate_ unique IDs. Couldn't Parameterized pass a deterministic value to Description.createTestDescription()?

I didn't mean that. I meant that Parameterized should _pass_ method.getName() + getName() as unique ID and method.getName() as name.

SGTM.

Even though we dropped the idea of having immutable Description instances for JUnit 4, should we add a builder to avoid having so many static creational methods?

Sure! 👍

Are there any estimates when it'll be fixed ?
This bug blocks related bug of JUnit5
https://github.com/junit-team/junit5/issues/549

@SqAutoTestTeam no one is actively working on this. I did update the Description builder branch recently so an ambitious contributor could start from there. Alternatively, we could add yet another static method to Description.

Fixing this would require new APIs on Description. We wouldn't want JUnit 5 to require those new APIs (currently JUnit 5 would work on just about any version of JUnit 4.x)

@diogoeag
Did you read this chapter in Surefire?
http://maven.apache.org/surefire/maven-surefire-plugin/examples/single-test.html#Multiple_Formats_in_One

Filtering parameterized tests works since of version 2.19. In your case it is -Dtest=TesClass#testName[*] instead of -Dtest=TesClass#testName.

@kcooney and @marcphilipp,

What do you guys thinking about "backporting" the _fix_ in https://github.com/junit-team/junit5/pull/1018/files#diff-42ed8b618527eb1521dbe3cd9100e949R48 to JUnit 4?

@sbrannen that looks like a reasonable change. I would just want to make sure that if we backported those changes we wouldn't make JUnit 4.13 incompatible with JUnit 5, or require projects with JUnit 4-style tests to upgrade to 4.13 before they can run their JUnit 4-style tests using the JUnit 5 runner APIs.

@kcooney,

that looks like a reasonable change.

👍

I would just want to make sure that if we backported those changes we wouldn't make JUnit 4.13 incompatible with JUnit 5, or require projects with JUnit 4-style tests to upgrade to 4.13 before they can run their JUnit 4-style tests using the JUnit 5 runner APIs.

In JUnit 4 the change would only be to a single line in Filter#matchMethodDescription(Description), and we can keep the same _fix_ in JUnit Vintage as it is now so that the VintageTestEngine would continue to work with JUnit 4.12.

So, unless I'm overlooking something, that should avoid any compatibility issues.

What do you think?

@sbrannen SGTM

Was this page helpful?
0 / 5 - 0 ratings