Ottieni il nome del test attualmente in esecuzione in JUnit 4


240

In JUnit 3, ho potuto ottenere il nome del test attualmente in esecuzione in questo modo:

public class MyTest extends TestCase
{
    public void testSomething()
    {
        System.out.println("Current test is " + getName());
        ...
    }
}

che stampa "Il test corrente è testSomething".

Esiste un modo semplice o immediato per farlo in JUnit 4?

Sfondo: Ovviamente, non voglio solo stampare il nome del test. Voglio caricare i dati specifici del test archiviati in una risorsa con lo stesso nome del test. Sai, convenzione sulla configurazione e tutto il resto.


Cosa ti dà il codice sopra in JUnit 4?
Bill the Lizard,

5
I test JUnit 3 estendono TestCase dove è definito getName (). I test di JUnit 4 non estendono una classe di base, quindi non esiste alcun metodo getName ().
Dave Ray,

Ho un problema simile in cui voglio <b> impostare </b> il nome del test poiché sto usando il Runner parametrizzato che mi dà solo casi di test numerati.
Volker Stolz,

Bella soluzione che utilizza Test o TestWatcher ... chiedendosi (ad alta voce) se ce ne sarebbe mai bisogno? Puoi scoprire se un test sta funzionando lentamente osservando i grafici di output dei tempi forniti da Gradle. Non dovresti mai aver bisogno di conoscere l'ordine in cui funzionano i test ...?
mike rodent,

Risposte:


378

JUnit 4.7 ha aggiunto questa funzionalità, sembra usare TestName-Rule . Sembra che questo ti darà il nome del metodo:

import org.junit.Rule;

public class NameRuleTest {
    @Rule public TestName name = new TestName();

    @Test public void testA() {
        assertEquals("testA", name.getMethodName());
    }

    @Test public void testB() {
        assertEquals("testB", name.getMethodName());
    }
}

4
Nota anche che TestName non è disponibile in @before :( Vedi: old.nabble.com/…
jm.

41
Apparentemente le versioni più recenti di JUnit vengono eseguite @Ruleprima @Before: sono nuovo su JUnit e dipendevo da TestNameme @Beforesenza alcuna difficoltà.
Potente


2
Se si utilizzano test con parametri "name.getMethodName ()" restituirà {testA [0], testA [1], ecc}, quindi ne uso alcuni come: assertTrue (name.getMethodName (). Match ("testA (\ [\ \ d \]) "));
Legna,

2
@DuncanJones Perché l'alternativa proposta è "più efficiente"?
Stephan,

117

JUnit 4.9.xe successive

Da JUnit 4.9, la TestWatchmanclasse è stata deprecata a favore della TestWatcherclasse, che ha invocazione:

@Rule
public TestRule watcher = new TestWatcher() {
   protected void starting(Description description) {
      System.out.println("Starting test: " + description.getMethodName());
   }
};

Nota: la classe contenente deve essere dichiarata public.

JUnit 4.7.x - 4.8.x

L'approccio seguente stamperà i nomi dei metodi per tutti i test in una classe:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};

1
@takacsot È sorprendente. Puoi per favore postare una nuova domanda a riguardo e mandarmi il link qui?
Duncan Jones,

Perché usare un publiccampo?
Raffi Khatchadourian,


16

JUnit 5 e successive

In JUnit 5 è possibile iniettare ciò TestInfoche semplifica i metadati di test fornendo ai metodi di test. Per esempio:

@Test
@DisplayName("This is my test")
@Tag("It is my tag")
void test1(TestInfo testInfo) {
    assertEquals("This is my test", testInfo.getDisplayName());
    assertTrue(testInfo.getTags().contains("It is my tag"));
}

Ulteriori informazioni: Manuale dell'utente di JUnit 5 , TestInfo javadoc .


9

Prova questo invece:

public class MyTest {
        @Rule
        public TestName testName = new TestName();

        @Rule
        public TestWatcher testWatcher = new TestWatcher() {
            @Override
            protected void starting(final Description description) {
                String methodName = description.getMethodName();
                String className = description.getClassName();
                className = className.substring(className.lastIndexOf('.') + 1);
                System.err.println("Starting JUnit-test: " + className + " " + methodName);
            }
        };

        @Test
        public void testA() {
                assertEquals("testA", testName.getMethodName());
        }

        @Test
        public void testB() {
                assertEquals("testB", testName.getMethodName());
        }
}

L'output è simile al seguente:

Starting JUnit-test: MyTest testA
Starting JUnit-test: MyTest testB

NOTA: NON funziona se il test è una sottoclasse di TestCase ! Il test viene eseguito ma il codice @Rule non viene mai eseguito.


3
Dio ti benedica per la tua NOTA proprio nell'esempio.
user655419

"Questo NON FUNZIONA" - caso a riguardo - il cetriolo ignora le annotazioni
@Rule

8

La possibilità di utilizzare SLF4J (Simple Logging Facade per Java) offre alcuni miglioramenti grazie ai messaggi con parametri. La combinazione di SLF4J con le implementazioni delle regole di JUnit 4 può fornire tecniche di registrazione delle classi di test più efficienti.

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.MethodRule;
import org.junit.rules.TestWatchman;
import org.junit.runners.model.FrameworkMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LoggingTest {

  @Rule public MethodRule watchman = new TestWatchman() {
    public void starting(FrameworkMethod method) {
      logger.info("{} being run...", method.getName());
    }
  };

  final Logger logger =
    LoggerFactory.getLogger(LoggingTest.class);

  @Test
  public void testA() {

  }

  @Test
  public void testB() {

  }
}

6

Un modo complicato è quello di creare il tuo Runner eseguendo la sottoclasse di org.junit.runners.BlockJUnit4ClassRunner.

È quindi possibile fare qualcosa del genere:

public class NameAwareRunner extends BlockJUnit4ClassRunner {

    public NameAwareRunner(Class<?> aClass) throws InitializationError {
        super(aClass);
    }

    @Override
    protected Statement methodBlock(FrameworkMethod frameworkMethod) {
        System.err.println(frameworkMethod.getName());
        return super.methodBlock(frameworkMethod);
    }
}

Quindi per ogni classe di test, dovrai aggiungere un'annotazione @RunWith (NameAwareRunner.class). In alternativa, potresti inserire quell'annotazione in una superclasse Test se non vuoi ricordarla ogni volta. Questo, ovviamente, limita la selezione dei corridori, ma potrebbe essere accettabile.

Inoltre, potrebbe essere necessario un po 'di kung fu per ottenere il nome del test corrente dal Runner e nel tuo framework, ma questo almeno ti dà il nome.


Almeno concettualmente, questa idea mi sembra piuttosto semplice. Il mio punto è: non lo definirei contorto.
user98761,

"on a Test superclass ..." - Per favore, non più degli orribili schemi di progettazione basati sull'eredità. Questo è così JUnit3!
Oberlies

3

JUnit 4 non ha alcun meccanismo predefinito per un caso di test per ottenere il proprio nome (anche durante l'installazione e lo smontaggio).


1
Esiste un meccanismo non pronto all'uso diverso dall'ispezione dello stack?
Dave Ray,

4
Non è il caso date le risposte qui sotto! forse assegnare la risposta corretta a qualcun altro?
Toby

3
String testName = null;
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
for (int i = trace.length - 1; i > 0; --i) {
    StackTraceElement ste = trace[i];
    try {
        Class<?> cls = Class.forName(ste.getClassName());
        Method method = cls.getDeclaredMethod(ste.getMethodName());
        Test annotation = method.getAnnotation(Test.class);
        if (annotation != null) {
            testName = ste.getClassName() + "." + ste.getMethodName();
            break;
        }
    } catch (ClassNotFoundException e) {
    } catch (NoSuchMethodException e) {
    } catch (SecurityException e) {
    }
}

1
Posso affermare che voleva solo mostrare una soluzione .. non capisco perché il voto negativo .... @downvoter: almeno, almeno, aggiungi informazioni utili ..
Victor

1
@skaffman Adoriamo tutti vedere l'intera gamma di soluzioni alternative. Questo è il più vicino per quello che sto cercando: ottenere il nome del test non direttamente nella classe di test ma nella classe che viene utilizzata durante il test (ad esempio da qualche parte in un componente logger). Lì, le annotazioni rilevanti per il test non funzionano più.
Daniel Alder,

3

Sulla base del commento precedente e considerando inoltre che ho creato un'estensione di TestWather che è possibile utilizzare nei metodi di test JUnit con questo:

public class ImportUtilsTest {
    private static final Logger LOGGER = Logger.getLogger(ImportUtilsTest.class);

    @Rule
    public TestWatcher testWatcher = new JUnitHelper(LOGGER);

    @Test
    public test1(){
    ...
    }
}

La classe helper di prova è la seguente:

public class JUnitHelper extends TestWatcher {
private Logger LOGGER;

public JUnitHelper(Logger LOGGER) {
    this.LOGGER = LOGGER;
}

@Override
protected void starting(final Description description) {
    LOGGER.info("STARTED " + description.getMethodName());
}

@Override
protected void succeeded(Description description) {
    LOGGER.info("SUCCESSFUL " + description.getMethodName());
}

@Override
protected void failed(Throwable e, Description description) {
    LOGGER.error("FAILURE " + description.getMethodName());
}
}

Godere!


Ciao, cos'è ImportUtilsTest, ricevo un errore, sembra essere una classe logger, ho più informazioni? Grazie
Sylhare,

1
La classe denominata è solo un esempio di una classe di test JUnit: l'utente di JUnitHelper. Correggerò l'esempio di utilizzo.
Csaba Tenkes,

Ah ora mi sento stupido, era così ovvio. Molte grazie! ;)
Sylhare,

1
@ClassRule
public static TestRule watchman = new TestWatcher() {
    @Override
    protected void starting( final Description description ) {
        String mN = description.getMethodName();
        if ( mN == null ) {
            mN = "setUpBeforeClass..";
        }

        final String s = StringTools.toString( "starting..JUnit-Test: %s.%s", description.getClassName(), mN );
        System.err.println( s );
    }
};

0

Ti suggerirei di disaccoppiare il nome del metodo di test dal tuo set di dati di test. Modellerei una classe DataLoaderFactory che carica / memorizza nella cache i set di dati di test dalle tue risorse, quindi nel tuo caso di test cam chiama un metodo di interfaccia che restituisce un set di dati di test per il caso di test. Avere i dati dei test legati al nome del metodo di test presuppone che i dati dei test possano essere utilizzati solo una volta, dove nella maggior parte dei casi suggerirei che gli stessi dati di test vengano utilizzati in più test per verificare vari aspetti della logica aziendale.


0

Puoi farlo usando Slf4jeTestWatcher

private static Logger _log = LoggerFactory.getLogger(SampleTest.class.getName());

@Rule
public TestWatcher watchman = new TestWatcher() {
    @Override
    public void starting(final Description method) {
        _log.info("being run..." + method.getMethodName());
    }
};

0

In JUnit 5 TestInfo funge da sostituzione drop-in per la regola TestName di JUnit 4.

Dalla documentazione:

TestInfo è utilizzato per iniettare informazioni sul test o contenitore corrente nei metodi @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll e @AfterAll.

Per recuperare il nome del metodo del test attualmente eseguito, sono disponibili due opzioni: String TestInfo.getDisplayName()e Method TestInfo.getTestMethod() .

Per recuperare solo il nome del metodo di prova corrente TestInfo.getDisplayName()potrebbe non essere sufficiente poiché il nome visualizzato predefinito del metodo di prova è methodName(TypeArg1, TypeArg2, ... TypeArg3).
Duplicazione dei nomi dei metodi in@DisplayName("..")Non è necessario una buona idea.

In alternativa è possibile utilizzare TestInfo.getTestMethod()che restituisce un Optional<Method>oggetto.
Se il metodo di recupero viene utilizzato all'interno di un metodo di test, non è nemmeno necessario testare il Optionalvalore di wrapping.

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.Test;

@Test
void doThat(TestInfo testInfo) throws Exception {
    Assertions.assertEquals("doThat(TestInfo)",testInfo.getDisplayName());
    Assertions.assertEquals("doThat",testInfo.getTestMethod().get().getName());
}

0

JUnit 5 tramite ExtensionContext

Vantaggio:

È possibile avere le funzionalità aggiunte ExtensionContextsostituendo afterEach(ExtensionContext context).

public abstract class BaseTest {

    protected WebDriver driver;

    @RegisterExtension
    AfterEachExtension afterEachExtension = new AfterEachExtension();

    @BeforeEach
    public void beforeEach() {
        // Initialise driver
    }

    @AfterEach
    public void afterEach() {
        afterEachExtension.setDriver(driver);
    }

}
public class AfterEachExtension implements AfterEachCallback {

    private WebDriver driver;

    public void setDriver(WebDriver driver) {
        this.driver = driver;
    }

    @Override
    public void afterEach(ExtensionContext context) {
        String testMethodName = context.getTestMethod().orElseThrow().getName();
        // Attach test steps, attach scsreenshots on failure only, etc.
        driver.quit();
    }

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.