ChatGPT解决这个技术问题 Extra ChatGPT

Conditionally ignoring tests in JUnit 4

OK, so the @Ignore annotation is good for marking that a test case shouldn't be run.

However, sometimes I want to ignore a test based on runtime information. An example might be if I have a concurrency test that needs to be run on a machine with a certain number of cores. If this test were run on a uniprocessor machine, I don't think it would be correct to just pass the test (since it hasn't been run), and it certainly wouldn't be right to fail the test and break the build.

So I want to be able to ignore tests at runtime, as this seems like the right outcome (since the test framework will allow the build to pass but record that the tests weren't run). I'm fairly sure that the annotation won't give me this flexibility, and suspect that I'll need to manually create the test suite for the class in question. However, the documentation doesn't mention anything about this and looking through the API it's also not clear how this would be done programmatically (i.e. how do I programatically create an instance of Test or similar that is equivalent to that created by the @Ignore annotation?).

If anyone has done something similar in the past, or has a bright idea of how else I could go about this, I'd be happy to hear about it.


t
tkruse

The JUnit way is to do this at run-time is org.junit.Assume.

 @Before
 public void beforeMethod() {
     org.junit.Assume.assumeTrue(someCondition());
     // rest of setup.
 }

You can do it in a @Before method or in the test itself, but not in an @After method. If you do it in the test itself, your @Before method will get run. You can also do it within @BeforeClass to prevent class initialization.

An assumption failure causes the test to be ignored.

Edit: To compare with the @RunIf annotation from junit-ext, their sample code would look like this:

@Test
public void calculateTotalSalary() {
    assumeThat(Database.connect(), is(notNull()));
    //test code below.
}

Not to mention that it is much easier to capture and use the connection from the Database.connect() method this way.


@notnoop, that isn't my observation at all. They are ignored. The IDEA test runner reports them that way, and a look at the JUnit source code shows that it reports the test as ignored.
To quote: "In the future, this may change, and a failed assumption may lead to the test being ignored." It in fact changed, as of 4.5 I believe. The current javadoc says: "The default JUnit runner treats tests with failing assumptions as ignored. Custom runners may behave differently." github.com/KentBeck/junit/blob/…
Eclipse 3.6 with Junit 4.8.1 reports false Assumptions as a passing test. Same with ant 1.8.1.
That Eclipse reports failed assumptions as passing is a bug: bugs.eclipse.org/bugs/show_bug.cgi?id=359944
@JeffStorey, then you are looking for a couple of things. One is the @BeforeClass annotation, where you can have your assumption fail there, which will skip the whole class. Another is @ClassRule (for the fine grained control, but over the whole class, one time).
R
Ryan Li

You should checkout Junit-ext project. They have RunIf annotation that performs conditional tests, like:

@Test
@RunIf(DatabaseIsConnected.class)
public void calculateTotalSalary() {
    //your code there
}

class DatabaseIsConnected implements Checker {
   public boolean satisify() {
        return Database.connect() != null;
   }
}

[Code sample taken from their tutorial]


Thanks for this answer - an interesting alternative syntax for the functionality, though I'll be going with Assume directly so as not to introduce another dependency.
I personally prefer this solution. If you have many tests that should be run based on the same conditions, this would be far more ideal than having to use Assume in every test. Also, if this can be used on a class level rather than the method level, it will be even more ideal.
I would prefer it, 'cuz this helps to run the test conditionally at run time. It suits where a number of unit test are going to run and the requirement is to run the unit tests on particular checker. I really amazed to see that junit-ext is not available on maven repository. How would we get avail this in maven project.
An annotation like @RunIf separates the condition when a test should run from the actual test code, which I think is good. What I don't like is that it requires a particular test runner. Therefore I wrote a JUnit rule to conditinoally ignore tests.
After installing the junit-ext jar (found here code.google.com/p/junit-ext/downloads/… ) in our local repository and implementing this @RunIf annotation... nothing! It is totally ignored, and I think the reason might be that junit-ext seems to depend on junit 4.5. We need 4.9+ due to spring-test. So... never mind that.
K
Kyle Shrader

In JUnit 4, another option for you may be to create an annotation to denote that the test needs to meet your custom criteria, then extend the default runner with your own and using reflection, base your decision on the custom criteria. It may look something like this:

public class CustomRunner extends BlockJUnit4ClassRunner {
    public CTRunner(Class<?> klass) throws initializationError {
        super(klass);
    }

    @Override
    protected boolean isIgnored(FrameworkMethod child) {
        if(shouldIgnore()) {
            return true;
        }
        return super.isIgnored(child);
    }

    private boolean shouldIgnore(class) {
        /* some custom criteria */
    }
}

While this looks nice and clean, it doesn't work with current versions if JUnit4, since the BlockJUnit4ClassRunner doesn't offer the isIgnored method anymore.
J
Juergen

Additionally to the answer of @tkruse and @Yishai:
I do this way to conditionally skip test methods especially for Parameterized tests, if a test method should only run for some test data records.

public class MyTest {
    // get current test method
    @Rule public TestName testName = new TestName();
    
    @Before
    public void setUp() {
        org.junit.Assume.assumeTrue(new Function<String, Boolean>() {
          @Override
          public Boolean apply(String testMethod) {
            if (testMethod.startsWith("testMyMethod")) {
              return <some condition>;
            }
            return true;
          }
        }.apply(testName.getMethodName()));
        
        ... continue setup ...
    }
}

S
Serg

A quick note: Assume.assumeTrue(condition) ignores rest of the steps but passes the test. To fail the test, use org.junit.Assert.fail() inside the conditional statement. Works same like Assume.assumeTrue() but fails the test.


As noted in the answers above, a failed assumption does not cause the test to pass, it returns a separate status. Some runners might erroneously report this as if it were a pass, but that's a weakness/bug in the test runner (and the default JUnit runner displays the test as ignored). And as for your final sentence, failing the test is specifically not what I want(ed) to do.
Oh OK. The tests passed on failed assumption in my case but I wanted them to be reported as failed (I was checking for an exception from Test Watcher). Forcing a failure helped me.