Uzyskaj nazwę aktualnie wykonywanego testu w JUnit 4

240

W JUnit 3 mogłem uzyskać nazwę aktualnie uruchomionego testu w następujący sposób:

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

które wyświetliłoby komunikat „Bieżący test to test”.

Czy w JUnit 4 jest jakaś gotowa lub prosta metoda?

Tło: Oczywiście nie chcę po prostu drukować nazwy testu. Chcę załadować dane specyficzne dla testu, które są przechowywane w zasobie o tej samej nazwie co test. Wiesz, konwencja o konfiguracji i tak dalej.

Dave Ray
źródło
Co daje ci powyższy kod w JUnit 4?
Bill the Lizard
5
Testy JUnit 3 rozszerzają TestCase tam, gdzie zdefiniowano getName (). Testy JUnit 4 nie rozszerzają klasy podstawowej, więc w ogóle nie ma metody getName ().
Dave Ray
Mam podobny problem, w którym chcę <b> ustawić </b> nazwę testu, ponieważ używam parametru Runner parametryzowanego, który daje mi tylko numerowane przypadki testowe.
Volker Stolz
Cudowne rozwiązanie przy użyciu Testu lub TestWatchera ... zastanawiam się (na głos), czy kiedykolwiek będzie taka potrzeba? Możesz sprawdzić, czy test działa powoli, patrząc na wykresy wyników czasowych podane przez Gradle. Nigdy nie powinieneś znać kolejności, w jakiej działają testy ...?
gryzoni Mike

Odpowiedzi:

378

JUnit 4.7 dodał tę funkcję, korzystając z reguły TestName . Wygląda na to, że otrzymasz nazwę metody:

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());
    }
}
FroMage
źródło
4
Należy również pamiętać, że nazwa_testowa nie jest dostępna w @before :( Patrz: old.nabble.com/…
jm.
41
Podobno nowsze wersje JUnit wykonać @Ruleprzed @Before- Jestem nowym JUnit i został zależności TestNamew moim @Beforebez żadnych trudności.
MightyE
9
Dostępne są bardziej wydajne sposoby wykonania tego .
Duncan Jones
2
Jeśli używasz sparametryzowanych testów, „name.getMethodName ()” zwróci {testA [0], testA [1] itd.}, Więc używam takich: assertTrue (name.getMethodName (). Mecze ("testA (\ [\ \re\])?"));
Legna
2
@DuncanJones Dlaczego proponowana alternatywa jest „bardziej wydajna”?
Stephan
117

JUnit 4.9.xi wyższe

Od JUnit 4.9 TestWatchmanklasa jest przestarzała na rzecz TestWatcherklasy, która ma wywołanie:

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

Uwaga: Klasa zawierająca musi zostać zadeklarowana public.

JUnit 4.7.x - 4.8.x

Następujące podejście wypisze nazwy metod dla wszystkich testów w klasie:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
źródło
1
@takacsot To zaskakujące. Czy możesz mi zadać nowe pytanie na ten temat i wysłać mi link tutaj?
Duncan Jones
Dlaczego warto korzystać z publicpola?
Raffi Khatchadourian
1
@RaffiKhatchadourian See stackoverflow.com/questions/14335558/...
Duncan Jones
16

JUnit 5 i nowszy

W JUnit 5 możesz wstrzykiwać, TestInfoco upraszcza metadane testowe zapewniające metody testowe. Na przykład:

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

Zobacz więcej: Podręcznik użytkownika JUnit 5 , TestInfo javadoc .

Andrii Abramov
źródło
9

Spróbuj zamiast tego:

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

Dane wyjściowe wyglądają następująco:

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

UWAGA: To NIE DZIAŁA, jeśli twój test jest podklasą TestCase ! Test działa, ale kod @Rule po prostu nigdy nie działa.

Yavin5
źródło
3
Niech Bóg was błogosławi za waszą UWAGA na samym przykładzie.
user655419,
„To NIE DZIAŁA” - przypadek - ogórek ignoruje adnotacje
@Rule
8

Zastanów się nad użyciem SLF4J (Simple Logging Facade for Java) zapewnia pewne udoskonalenia za pomocą sparametryzowanych komunikatów. Połączenie SLF4J z implementacjami reguł JUnit 4 może zapewnić bardziej wydajne techniki rejestrowania klas testów.

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() {

  }
}
czeladnik
źródło
6

Skręconym sposobem jest utworzenie własnego Runnera przez podklasowanie org.junit.runners.BlockJUnit4ClassRunner.

Następnie możesz zrobić coś takiego:

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

Następnie dla każdej klasy testowej musisz dodać adnotację @RunWith (NameAwareRunner.class). Ewentualnie możesz umieścić tę adnotację w nadklasie testowej, jeśli nie chcesz jej pamiętać za każdym razem. To oczywiście ogranicza wybór biegaczy, ale może to być do zaakceptowania.

Może również zająć trochę kung fu, aby pobrać bieżącą nazwę testową z Runnera do twojego frameworka, ale to przynajmniej zapewni ci nazwę.

chris.f.jones
źródło
Przynajmniej koncepcyjnie ten pomysł wydaje mi się dość prosty. Chodzi mi o to: nie nazwałbym tego zawiłym.
user98761,
„na nadklasie testowej ...” - Proszę, nie więcej okropnych wzorców projektowych opartych na dziedzictwie. To jest tak JUnit3!
oberlies
3

JUnit 4 nie ma gotowego mechanizmu do testowania, aby uzyskać własną nazwę (w tym podczas instalacji i porzucenia).

cordellcp3
źródło
1
Czy istnieje jakiś inny, niż zwykle, mechanizm sprawdzania stosu?
Dave Ray
4
Nie tak, biorąc pod uwagę poniższe odpowiedzi! może przypisać poprawną odpowiedź komuś innemu?
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) {
    }
}
jnorris
źródło
1
Mogę argumentować, że chciał tylko pokazać rozwiązanie .. nie rozumiem, dlaczego głosowanie negatywne .... @downvoter: przynajmniej przynajmniej dodaj przydatne informacje ..
Victor
1
@skaffman Wszyscy lubimy widzieć pełną gamę alternatywnych rozwiązań. Jest to najbliższe, czego szukam: uzyskanie nazwy testu nie bezpośrednio w klasie testowej, ale w klasie, która jest używana podczas testu (na przykład gdzieś w komponencie logger). Tam adnotacje związane z testem już nie działają.
Daniel Alder,
3

W oparciu o poprzedni komentarz i dalsze rozważania stworzyłem rozszerzenie TestWather, którego można użyć w metodach testowych JUnit za pomocą tego:

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

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

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

Klasa pomocnika testowego jest następna:

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

Cieszyć się!

Csaba Tenkes
źródło
Cześć, co to jest, pojawia ImportUtilsTestsię błąd, wygląda na to, że jest to klasa rejestratora, czy mam więcej informacji? Dzięki
Sylhare,
1
Nazwana klasa jest tylko przykładem klasy testowej JUnit: użytkownik JUnitHelper. Poprawię przykład użycia.
Csaba Tenkes,
Ach, teraz czuję się głupi, to było takie oczywiste. Wielkie dzięki! ;)
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 );
    }
};
leojkelav
źródło
0

Proponuję oddzielić nazwę metody testowej od zestawu danych testowych. Modelowałbym klasę DataLoaderFactory, która ładuje / buforuje zestawy danych testowych z twoich zasobów, a następnie w twoim przypadku testowym kamera wywołuje metodę interfejsu, która zwraca zestaw danych testowych dla przypadku testowego. Powiązanie danych testowych z nazwą metody testowej zakłada, że ​​danych testowych można użyć tylko raz, w większości przypadków sugerowałbym, aby te same dane testowe były używane w wielu testach w celu weryfikacji różnych aspektów logiki biznesowej.

szmaragdowa
źródło
0

Możesz to osiągnąć za pomocą Slf4jiTestWatcher

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());
    }
};
Koder
źródło
0

W JUnit 5 TestInfo działa jako drop-in zamiennik reguły TestName z JUnit 4.

Z dokumentacji:

TestInfo służy do wstrzykiwania informacji o bieżącym teście lub pojemniku do metod @Test, @RepeatedTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll i @AfterAll.

Aby pobrać nazwę metody aktualnie wykonywanego testu, masz dwie opcje: String TestInfo.getDisplayName()i Method TestInfo.getTestMethod() .

Aby pobrać tylko nazwę bieżącej metody testowej, TestInfo.getDisplayName()może nie wystarczyć, ponieważ domyślna nazwa wyświetlana metody testowej to methodName(TypeArg1, TypeArg2, ... TypeArg3).
Powielanie nazw metod w@DisplayName("..") nie jest koniecznym dobrym pomysłem.

Alternatywnie możesz użyć TestInfo.getTestMethod()zwracającego Optional<Method>obiekt.
Jeśli metoda pobierania jest używana w metodzie testowej, nie trzeba nawet testować Optionalopakowanej wartości.

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());
}
davidxxx
źródło
0

JUnit 5 przez ExtensionContext

Korzyść:

Możesz mieć dodatkowe funkcje ExtensionContextzastępowania 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();
    }

}
srebro
źródło