155

Like the title says, I'm looking for some simple way to run JUnit 4.x tests several times in a row automatically using Eclipse.

An example would be running the same test 10 times in a row and reporting back the result.

We already have a complex way of doing this but I'm looking for a simple way of doing it so that I can be sorta sure that the flaky test I've been trying to fix stays fixed.

An ideal solution would be an Eclipse plugin/setting/feature that I am unaware of.

Stefan Thyberg
  • 3,445
  • 3
  • 23
  • 29

11 Answers11

140

The easiest (as in least amount of new code required) way to do this is to run the test as a parametrized test (annotate with an @RunWith(Parameterized.class) and add a method to provide 10 empty parameters). That way the framework will run the test 10 times.

This test would need to be the only test in the class, or better put all test methods should need to be run 10 times in the class.

Here is an example:

@RunWith(Parameterized.class)
public class RunTenTimes {

    @Parameterized.Parameters
    public static Object[][] data() {
        return new Object[10][0];
    }

    public RunTenTimes() {
    }

    @Test
    public void runsTenTimes() {
        System.out.println("run");
    }
}

With the above, it is possible to even do it with a parameter-less constructor, but I'm not sure if the framework authors intended that, or if that will break in the future.

If you are implementing your own runner, then you could have the runner run the test 10 times. If you are using a third party runner, then with 4.7, you can use the new @Rule annotation and implement the MethodRule interface so that it takes the statement and executes it 10 times in a for loop. The current disadvantage of this approach is that @Before and @After get run only once. This will likely change in the next version of JUnit (the @Before will run after the @Rule), but regardless you will be acting on the same instance of the object (something that isn't true of the Parameterized runner). This assumes that whatever runner you are running the class with correctly recognizes the @Rule annotations. That is only the case if it is delegating to the JUnit runners.

If you are running with a custom runner that does not recognize the @Rule annotation, then you are really stuck with having to write your own runner that delegates appropriately to that Runner and runs it 10 times.

Note that there are other ways to potentially solve this (such as the Theories runner) but they all require a runner. Unfortunately JUnit does not currently support layers of runners. That is a runner that chains other runners.

Stefan Birkner
  • 24,059
  • 12
  • 57
  • 72
Yishai
  • 90,445
  • 31
  • 189
  • 263
  • 2
    Unfortunately I'm already running @RunWith with another runner, but otherwise this would have been an ideal solution. – Stefan Thyberg Sep 30 '09 at 08:22
  • Yes, this is the solution that I would like to have though and which will be best for most people so I'm going to go ahead and accept the answer. – Stefan Thyberg Oct 01 '09 at 09:18
  • For an alternative and possibly less hacky solution see: http://stackoverflow.com/a/21349010/281545 – Mr_and_Mrs_D Jan 25 '17 at 18:35
  • Nice solution! I got an exception telling me that the data method should return an Iterable of Arrays. I fixed it accordingly: @Parameterized.Parameters public static Iterable data() { return Arrays.asList(new Object[20][0]); } – nadre Aug 09 '18 at 08:03
  • 1
    Could you please link to [this answer](https://stackoverflow.com/a/45321618) for JUnit 5? It describes the requested feature which was added in JUnit 5 – Marcono1234 Sep 22 '18 at 16:54
  • Much easier than trying to apply a TestRule. At least for me. – TalL Dec 03 '20 at 10:07
123

With IntelliJ, you can do this from the test configuration. Once you open this window, you can choose to run the test any number of times you want,.

enter image description here

when you run the test, intellij will execute all tests you have selected for the number of times you specified.

Example running 624 tests 10 times: enter image description here

smac89
  • 39,374
  • 15
  • 132
  • 179
  • 5
    That's perfect, now if you can point to an eclipse way of doing this, that would answer the OP's question to the point – khal Nov 15 '17 at 01:33
  • 1
    Relying on a specific tool to host actual logic or requirements is an anti-pattern. – Mickael Jun 11 '19 at 14:18
  • 1
    @Mickael Repeating a test N times is usually not a requirement of testing. In fact tests should be deterministic, so that no matter how many times it is repeated, it should always produce the same result. Can you explain the anti pattern you speak of? – smac89 Jun 11 '19 at 14:45
  • 1
    If repeating a test has been useful for 1 developer, it's likely to be useful for others. So if the test runtime and the code can host the logic to enable repetition, it should be preferred as it allows to factorize the effort and solution and allow contributors to use the tool the want with the same result. Putting reusable logic in the IDE/developer area when it can be put in the code is kind of missing factorization. – Mickael Jun 12 '19 at 14:37
  • 2
    In the latest IDEA version (v2021.2.3) this is hidden under 'Modify Options (Alt+M) -> Tests | Repeat - Once', selecting N times makes the text box for no. of repetitions appear – aksh1618 Nov 24 '21 at 07:45
88

With JUnit 5 I was able to solve this using the @RepeatedTest annotation:

@RepeatedTest(10)
public void testMyCode() {
    //your test code goes here
}

Note that @Test annotation shouldn't be used along with @RepeatedTest.

César Alberca
  • 2,321
  • 2
  • 20
  • 32
76

I've found that Spring's repeat annotation is useful for that kind of thing:

@Repeat(value = 10)

Latest (Spring Framework 4.3.11.RELEASE API) doc:

David Tonhofer
  • 14,559
  • 5
  • 55
  • 51
laura
  • 7,280
  • 4
  • 35
  • 43
  • 59
    Changing test frameworks is not what I would call an easy way of doing it. – Stefan Thyberg Sep 29 '09 at 14:52
  • 3
    You don't need to change your test framework - it works fine with JUnit. The main drawback is that JUnit still sees it as a single test. So the first time it break, execution will stop. However, if you are not already using Spring, then it is probably not the way you want to go... – tveon Dec 16 '15 at 11:54
  • Doesn't seem to work for me (Spring 4.3.6 via Spring Boot 1.5.1) – David Tonhofer Sep 13 '17 at 15:00
  • Does not work for me with spring boot 2.1.6 and junit 5 – jo- Sep 16 '19 at 08:43
  • Works perfectly with spring boot 2. Don't forget to add @RunWith(SpringRunner::class), as per the poster's 'unit testing in spring' link! – Agoston Horvath Dec 11 '19 at 10:33
37

Inspired by the following resources:

Example

Create and use a @Repeat annotation as follows:

public class MyTestClass {

    @Rule
    public RepeatRule repeatRule = new RepeatRule();

    @Test
    @Repeat(10)
    public void testMyCode() {
        //your test code goes here
    }
}

Repeat.java

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.METHOD;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention( RetentionPolicy.RUNTIME )
@Target({ METHOD, ANNOTATION_TYPE })
public @interface Repeat {
    int value() default 1;
}

RepeatRule.java

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class RepeatRule implements TestRule {

    private static class RepeatStatement extends Statement {
        private final Statement statement;
        private final int repeat;    

        public RepeatStatement(Statement statement, int repeat) {
            this.statement = statement;
            this.repeat = repeat;
        }

        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < repeat; i++) {
                statement.evaluate();
            }
        }

    }

    @Override
    public Statement apply(Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.value();
            result = new RepeatStatement(statement, times);
        }
        return result;
    }
}

PowerMock

Using this solution with @RunWith(PowerMockRunner.class), requires updating to Powermock 1.6.5 (which includes a patch).

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
R. Oosterholt
  • 7,720
  • 2
  • 53
  • 77
  • I'm not using eclipse myself. Maybe you are not using a junut 4 test runner? (see [doc "Customizing a Test Configuration"](http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.jdt.doc.user%2FgettingStarted%2Fqs-junit.htm)) – R. Oosterholt Apr 30 '15 at 07:30
  • This should be the best solution for JUnit 4. – CHANist Jan 08 '23 at 00:00
15

Anything wrong with:

@Test
void itWorks() {
    // stuff
}

@Test
void itWorksRepeatably() {
    for (int i = 0; i < 10; i++) {
        itWorks();
    }
}

Unlike the case where you are testing each of an array of values, you don't particularly care which run failed.

No need to do in configuration or annotation what you can do in code.

Dmitry Ginzburg
  • 7,391
  • 2
  • 37
  • 48
soru
  • 5,464
  • 26
  • 30
  • 3
    I'd like to run several tests as normal unit tests and get a trace and status for each one. – Stefan Thyberg Oct 01 '09 at 09:19
  • 33
    In this case "@Before"s and "@After"s won't be run – Bogdan Nov 06 '14 at 12:45
  • 3
    This along with manually calling the `@Before` annotated method before `itWorks()` solved my problem. – João Neves Sep 18 '15 at 12:01
  • Do you know the DRY concept? https://en.wikipedia.org/wiki/Don%27t_repeat_yourself I recommend to make some setup instead of copy pasting your loop everywhere. – Kikiwa Jan 27 '16 at 10:28
  • The edit queue for this answer is full; therefore, I'll put it in a comment: for JUnit4, the tests need to be public. – Richard Jessop Apr 29 '20 at 01:49
  • The Eclipse JUnit pane will not report any repeated-test failures; the repeated test is executed outside the JUnit framework. – Richard Jessop Apr 29 '20 at 01:59
  • We know that isn't right, but i always give credits for good idea. In Brazil we say "Gambiarra", that means like "work around". Have one simple and ridiculous solution is better then none. – Sham Fiorin Mar 12 '21 at 16:31
7

This works much easier for me.

public class RepeatTests extends TestCase {

    public static Test suite() {
        TestSuite suite = new TestSuite(RepeatTests.class.getName());

        for (int i = 0; i < 10; i++) {              
        suite.addTestSuite(YourTest.class);             
        }

        return suite;
    }
}
Qualk
  • 71
  • 1
  • 5
  • Awesome as doesn't use another framework and actually works with JUnit 3(crucial for android) – Vladimir Ivanov May 27 '15 at 15:36
  • 1
    An implementation with JUnit4 could be done with a Runner: `public class RepeatRunner extends BlockJUnit4ClassRunner { public RepeatRunner(Class klass) throws InitializationError { super(klass); } @Override public void run(final RunNotifier notifier) { for (int i = 0; i < 10; i++) { super.run(notifier); } } }` Although at least in Eclipse JUnit plugin you get results like: "10/1 tests passed" – Peter Wippermann Feb 07 '16 at 15:14
7

There's an Intermittent annotation in the tempus-fugit library which works with JUnit 4.7's @Rule to repeat a test several times or with @RunWith.

For example,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

   @Test
   @Intermittent(repition = 99)
   public void annotatedTest() {
      testCounter++;
   }
}

After the test is run (with the IntermittentTestRunner in the @RunWith), testCounter would be equal to 99.

Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
Toby
  • 9,523
  • 8
  • 36
  • 59
  • Yeah, it's the same problem here, already using another runner and so can't use this one, good idea though. – Stefan Thyberg Jan 12 '10 at 12:58
  • Yeah, I'm having the same issue with RunWith... as it goes I tweaked tempus-fugit to get round it a little, you can use a @Rule rather than runner when you want to run repeatedly. You mark it up with @Repeating instead of intermittent. The rule version wont run @Before/@Afters though. See http://tempus-fugit.googlecode.com/svn/site/documentation/concurrency.html#JUnit_Integration (scroll down to load/soak testing) for more details. – Toby Jan 19 '10 at 19:27
2

This is essentially the answer that Yishai provided above, re-written in Kotlin :

@RunWith(Parameterized::class)
class MyTest {

    companion object {

        private const val numberOfTests = 200

        @JvmStatic
        @Parameterized.Parameters
        fun data(): Array<Array<Any?>> = Array(numberOfTests) { arrayOfNulls<Any?>(0) }
    }

    @Test
    fun testSomething() { }
}
bob
  • 126
  • 9
0

I build a module that allows do this kind of tests. But it is focused not only in repeat. But in guarantee that some piece of code is Thread safe.

https://github.com/anderson-marques/concurrent-testing

Maven dependency:

<dependency>
    <groupId>org.lite</groupId>
    <artifactId>concurrent-testing</artifactId>
    <version>1.0.0</version>
</dependency>

Example of use:

package org.lite.concurrent.testing;

import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import ConcurrentTest;
import ConcurrentTestsRule;

/**
 * Concurrent tests examples
 */
public class ExampleTest {

    /**
     * Create a new TestRule that will be applied to all tests
     */
    @Rule
    public ConcurrentTestsRule ct = ConcurrentTestsRule.silentTests();

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 20, threads = 10)
    public void testConcurrentExecutionSuccess(){
        Assert.assertTrue(true);
    }

    /**
     * Tests using 10 threads and make 20 requests. This means until 10 simultaneous requests.
     */
    @Test
    @ConcurrentTest(requests = 200, threads = 10, timeoutMillis = 100)
    public void testConcurrentExecutionSuccessWaitOnly100Millissecond(){
    }

    @Test(expected = RuntimeException.class)
    @ConcurrentTest(requests = 3)
    public void testConcurrentExecutionFail(){
        throw new RuntimeException("Fail");
    }
}

This is a open source project. Feel free to improve.

Anderson Marques
  • 808
  • 8
  • 13
0

You could run your JUnit test from a main method and repeat it so many times you need:

package tests;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.Result;

public class RepeatedTest {

    @Test
    public void test() {
        fail("Not yet implemented");
    }

    public static void main(String args[]) {

        boolean runForever = true;

        while (runForever) {
            Result result = org.junit.runner.JUnitCore.runClasses(RepeatedTest.class);

            if (result.getFailureCount() > 0) {
                runForever = false;
               //Do something with the result object

            }
        }

    }

}
silver_mx
  • 828
  • 9
  • 16