Maneira fácil de executar o mesmo teste junit repetidamente?

121

Como o título diz, estou procurando uma maneira simples de executar os testes do JUnit 4.x várias vezes seguidas automaticamente usando o Eclipse.

Um exemplo seria executar o mesmo teste 10 vezes seguidas e reportar o resultado.

Já temos uma maneira complexa de fazer isso, mas estou procurando uma maneira simples de fazê-lo, para que eu possa ter certeza de que o teste superficial que estou tentando corrigir permanece fixo.

Uma solução ideal seria um plugin / configuração / recurso do Eclipse que eu desconheço.

Stefan Thyberg
fonte
5
Estou muito curioso sobre o porquê de você querer fazer isso.
Buhb
Estou executando um grande teste de caixa preta, fiz uma pequena alteração e quero ver como isso afetou a estabilidade desse teste anteriormente escamoso.
27713 Stefan Thyberg
É verdade, exceto que você deseja que ele funcione até a falha, enquanto eu só quero executá-lo várias vezes, o que pode afetar as respostas que recebo.
Stefan Thyberg
4
Você é contra o TestNG porque, se não, você pode usar o @Test (invocationCount = 10) e isso é tudo o que existe.
Robert Massaioli 12/03
1
Eu não era "contra" o TestNG, simplesmente não o estávamos usando nesse projeto.
Stefan Thyberg

Respostas:

123

A maneira mais fácil (como é necessária a quantidade mínima de código novo) para fazer isso é executar o teste como um teste parametrizado (anote com @RunWith(Parameterized.class)e adicione um método para fornecer 10 parâmetros vazios). Dessa forma, a estrutura executará o teste 10 vezes.

Esse teste precisaria ser o único na classe, ou melhor, todos os métodos de teste precisariam ser executados 10 vezes na classe.

Aqui está um exemplo:

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

Com o exposto, é possível fazê-lo com um construtor sem parâmetros, mas não tenho certeza se os autores da estrutura pretendiam isso ou se isso seria interrompido no futuro.

Se você estiver implementando seu próprio corredor, poderá fazer com que o corredor execute o teste 10 vezes. Se você estiver usando um corredor de terceiros, com o 4.7, poderá usar a nova @Ruleanotação e implementar a MethodRuleinterface para que ela pegue a instrução e a execute 10 vezes em um loop for. A desvantagem atual dessa abordagem é essa @Beforee @Afteré executada apenas uma vez. Provavelmente, isso mudará na próxima versão do JUnit (o que @Beforeserá executado após o @Rule), mas independentemente você estará agindo na mesma instância do objeto (algo que não é verdadeiro para o Parameterizedcorredor). Isso pressupõe que qualquer corredor com o qual você esteja executando a classe reconheça corretamente as @Ruleanotações. Esse é apenas o caso se estiver delegando para os corredores JUnit.

Se você estiver executando com um corredor personalizado que não reconheça a @Ruleanotação, ficará realmente com a necessidade de escrever seu próprio corredor, que delega adequadamente esse corredor e o executa 10 vezes.

Observe que existem outras maneiras de resolver isso potencialmente (como o corredor de teorias), mas todas elas exigem um corredor. Infelizmente, o JUnit atualmente não suporta camadas de corredores. Esse é um corredor que liga outros corredores.

Yishai
fonte
2
Infelizmente, já estou executando o @RunWith com outro corredor, mas, caso contrário, essa seria uma solução ideal.
27613 Stefan Thyberg
Sim, essa é a solução que eu gostaria de ter e qual será a melhor para a maioria das pessoas, por isso vou em frente e aceito a resposta.
Stefan Thyberg
Para uma solução alternativa e possivelmente menos hacky, consulte: stackoverflow.com/a/21349010/281545
Mr_and_Mrs_D
Ótima solução! Eu recebi uma exceção dizendo que o método de dados deve retornar um Iterable of Arrays. Corrigi-o de acordo: @ Parameterized.Parameters public static Iterable <Object []> data () {return Arrays.asList (new Object [20] [0]); }
nadre
1
Você poderia vincular a esta resposta para o JUnit 5? Ele descreve o recurso solicitado que foi adicionado no JUnit 5
Marcono1234 22/09
100

Com o IntelliJ, você pode fazer isso na configuração de teste. Depois de abrir esta janela, você pode optar por executar o teste quantas vezes quiser.

insira a descrição da imagem aqui

Quando você executa o teste, o intellij executará todos os testes selecionados para o número de vezes que você especificou.

Exemplo executando 624 testes 10 vezes: insira a descrição da imagem aqui

smac89
fonte
4
Isso é perfeito, agora, se você puder apontar uma maneira
eclética
Confiar em uma ferramenta específica para hospedar a lógica ou os requisitos reais é um antipadrão.
Mickael
1
@Mickael Repetir um teste N vezes geralmente não é um requisito de teste. De fato, os testes devem ser determinísticos, de modo que, não importa quantas vezes sejam repetidos, devem sempre produzir o mesmo resultado. Você pode explicar o anti-padrão de que você fala?
smac89
Se a repetição de um teste tiver sido útil para 1 desenvolvedor, provavelmente será útil para outros. Portanto, se o tempo de execução do teste e o código puderem hospedar a lógica para permitir a repetição, deve ser preferido, pois permite fatorar o esforço e a solução e permitir que os colaboradores usem a ferramenta desejada com o mesmo resultado. Colocar lógica reutilizável na área de IDE / desenvolvedor quando ela pode ser inserida no código é uma falta de fatoração.
Mickael
68

Descobri que a anotação repetida do Spring é útil para esse tipo de coisa:

@Repeat(value = 10)

Documento mais recente (API Spring Framework 4.3.11.RELEASE):

laura
fonte
46
Alterar estruturas de teste não é o que eu chamaria de maneira fácil de fazer isso.
23913 Stefan Thyberg
3
Você não precisa alterar sua estrutura de teste - ela funciona bem com o JUnit. A principal desvantagem é que o JUnit ainda o vê como um único teste. Portanto, na primeira vez que ele quebrar, a execução será interrompida. No entanto, se você ainda não estiver usando o Spring, provavelmente não é esse o caminho que deseja seguir ...
tveon
Parece não funcionar para mim (Spring 4.3.6 via Spring Boot 1.5.1)
David Tonhofer
Não funciona para mim com boot de primavera 2.1.6 e junit 5
jo-
Funciona perfeitamente com a bota de mola 2. Não se esqueça de adicionar @RunWith (SpringRunner :: class), conforme o link 'teste de unidade na primavera' do pôster!
Agoston Horvath
33

Inspirado nos seguintes recursos:

Exemplo

Crie e use uma @Repeatanotação da seguinte maneira:

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

Para usar esta solução @RunWith(PowerMockRunner.class), é necessário atualizar para o Powermock 1.6.5 (que inclui um patch ).

R. Oosterholt
fonte
Sim. Como você está executando os testes?
R. Oosterholt
Eu não estou usando eclipse sozinho. Talvez você não esteja usando um corredor de teste de 4 de junho? (consulte o documento "Personalizando uma configuração de teste" )
R. Oosterholt
29

Com o JUnit 5, consegui resolver isso usando a anotação @RepeatedTest :

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

Observe que a @Testanotação não deve ser usada junto com @RepeatedTest.

César Alberca
fonte
Parece muito promissor, apenas para observar que ainda não há uma versão de lançamento.
precisa saber é o seguinte
1
O JUnit 5 atingiu o GA há algumas semanas.
Mkobit
11

Algo errado com:

@Test
void itWorks() {
    // stuff
}

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

Ao contrário do caso em que você está testando cada uma de uma matriz de valores, não se importa particularmente com a falha na execução.

Não é necessário fazer na configuração ou anotação o que você pode fazer no código.

soru
fonte
2
Eu gostaria de executar vários testes como testes de unidade normais e obter um rastreio e status para cada um.
277 Stefan Thyberg #
24
Neste caso, "@Before" s e "@After" s não vai ser executado
Bogdan
3
Isso, juntamente com a chamada manual do @Beforemétodo anotado, antes itWorks() resolveu o meu problema.
João Neves
Você conhece o conceito DRY? pt.wikipedia.org/wiki/Don%27t_repeat_yourself Eu recomendo fazer algumas configurações em vez de copiar e colar seu loop em todos os lugares.
Kikiwa
A fila de edição para esta resposta está cheia; portanto, colocarei em um comentário: para o JUnit4, os testes precisam ser públicos.
Richard Jessop
7

Isso funciona muito mais fácil para mim.

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
fonte
Impressionante como não usar outro quadro e realmente trabalha com JUnit 3 (crucial para android)
Vladimir Ivanov
1
Uma implementação com JUnit4 poderia ser feita com um 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); } } }Embora pelo menos no plug-in Eclipse JUnit você obtenha resultados como: "10/1 testes aprovados"
Peter Wippermann
7

Há uma anotação intermitente na biblioteca tempus-fugit que funciona com o JUnit 4.7 @Rulepara repetir um teste várias vezes ou com@RunWith .

Por exemplo,

@RunWith(IntermittentTestRunner.class)
public class IntermittentTestRunnerTest {

   private static int testCounter = 0;

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

Após a execução do teste (com o IntermittentTestRunner no @RunWith), testCounterseria igual a 99.

Toby
fonte
Sim, é o mesmo problema aqui, já usando outro corredor e, portanto, não pode usar este, boa ideia.
Stefan Thyberg 12/01
Sim, estou tendo o mesmo problema com o RunWith ... como eu ajustei o tempus-fugit para contornar um pouco, você pode usar uma @Rule em vez de runner quando quiser executar repetidamente. Você o marca com @Repeating, em vez de intermitente. A versão da regra não será executada @ Antes / @ Depois. Consulte tempus-fugit.googlecode.com/svn/site/documentation/… (role para baixo para carregar / absorver testes) para obter mais detalhes.
Toby
0

Eu construo um módulo que permite fazer esse tipo de teste. Mas é focado não apenas na repetição. Mas, para garantir que algum trecho de código seja seguro para threads.

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

Dependência do Maven:

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

Exemplo de uso:

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

Este é um projeto de código aberto. Sinta-se livre para melhorar.

Anderson Marques
fonte
0

Você pode executar seu teste JUnit a partir de um método principal e repeti-lo quantas vezes precisar:

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
fonte
0

Esta é essencialmente a resposta que Yishai forneceu acima, reescrita em 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() { }
}
marca
fonte