Dapatkan nama tes yang sedang dilaksanakan di JUnit 4

240

Di JUnit 3, saya bisa mendapatkan nama tes yang sedang berjalan seperti ini:

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

yang akan mencetak "Tes saat ini adalah testSomething".

Apakah ada cara sederhana atau sederhana untuk melakukan ini di JUnit 4?

Latar Belakang: Jelas, saya tidak ingin hanya mencetak nama ujian. Saya ingin memuat data khusus pengujian yang disimpan dalam sumber daya dengan nama yang sama dengan pengujian. Anda tahu, konvensi tentang konfigurasi dan semua itu.

Dave Ray
sumber
Apa yang diberikan kode di atas di JUnit 4?
Bill the Lizard
5
Uji JUnit 3 memperpanjang TestCase di mana getName () didefinisikan. JUnit 4 tes tidak memperpanjang kelas dasar, jadi tidak ada metode getName () sama sekali.
Dave Ray
Saya memiliki masalah yang sama di mana saya ingin <b> mengatur </b> nama tes karena saya menggunakan pelari Parametrized yang hanya memberi saya jumlah kasus uji.
Volker Stolz
Solusi yang bagus menggunakan Test atau TestWatcher ... hanya bertanya-tanya (dengan suara keras) apakah harus ada kebutuhan untuk ini? Anda dapat menemukan apakah tes berjalan lambat dengan melihat grafik output waktu yang diberikan oleh Gradle. Anda seharusnya tidak perlu mengetahui urutan di mana tes beroperasi ...?
mike rodent

Jawaban:

378

JUnit 4.7 menambahkan fitur ini sepertinya menggunakan TestName-Rule . Sepertinya ini akan memberi Anda nama metode:

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
sumber
4
Juga perhatikan bahwa TestName tidak tersedia di @ sebelum :( Lihat: old.nabble.com/...
jm.
41
Rupanya versi yang lebih baru dari JUnit dieksekusi @Rulesebelumnya @Before- saya baru di JUnit dan tergantung pada TestNamesaya @Beforetanpa kesulitan.
MightyE
9
Ada cara yang lebih efisien untuk melakukan hal ini .
Duncan Jones
2
Jika Anda menggunakan tes parameterisasi "name.getMethodName ()" akan mengembalikan {testA [0], testA [1], dll} jadi saya menggunakan beberapa seperti: assertTrue (name.getMethodName (). Cocok ("testA (\ [\ \ d \])? "));
Legna
2
@DuncanJones Mengapa alternatif yang diusulkan "lebih efisien"?
Stephan
117

JUnit 4.9.x dan lebih tinggi

Sejak JUnit 4.9, TestWatchmankelas tidak lagi mendukung TestWatcherkelas, yang memiliki doa:

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

Catatan: Kelas yang mengandung harus dideklarasikan public.

JUnit 4.7.x - 4.8.x

Pendekatan berikut akan mencetak nama metode untuk semua tes di kelas:

@Rule
public MethodRule watchman = new TestWatchman() {
   public void starting(FrameworkMethod method) {
      System.out.println("Starting test: " + method.getName());
   }
};
Duncan Jones
sumber
1
@takacsot Itu mengejutkan. Bisakah Anda memposting pertanyaan baru tentang ini dan ping saya tautannya di sini?
Duncan Jones
Mengapa menggunakan publicbidang?
Raffi Khatchadourian
1
@RaffiKhatchadourian Lihat stackoverflow.com/questions/14335558/…
Duncan Jones
16

JUnit 5 dan lebih tinggi

Di JUnit 5 Anda dapat menyuntikkan TestInfoyang menyederhanakan meta data yang menyediakan metode pengujian. Sebagai contoh:

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

Lihat lebih lanjut: Panduan Pengguna JUnit 5 , TestInfo javadoc .

Andrii Abramov
sumber
9

Coba ini sebagai gantinya:

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

Outputnya terlihat seperti ini:

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

CATATAN: Ini TIDAK berfungsi jika tes Anda adalah subkelas dari TestCase ! Tes berjalan tetapi kode @Rule tidak pernah berjalan.

Yavin5
sumber
3
Tuhan memberkati Anda untuk CATATAN Anda di bagian paling bawah dari contoh ini.
user655419
"Ini TIDAK berfungsi" - contohnya - mentimun mengabaikan penjelasan
@Rule
8

Pertimbangkan untuk menggunakan SLF4J (Fasad Penebangan Sederhana untuk Java) memberikan beberapa perbaikan yang rapi menggunakan pesan parameter. Menggabungkan SLF4J dengan implementasi aturan JUnit 4 dapat memberikan teknik logging kelas uji yang lebih efisien.

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

  }
}
pekerja harian
sumber
6

Cara berbelit-belit adalah dengan membuat Runner Anda sendiri dengan mensubklasifikasikan org.junit.runners.BlockJUnit4ClassRunner.

Anda kemudian dapat melakukan sesuatu seperti ini:

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

Kemudian untuk setiap kelas tes, Anda harus menambahkan penjelasan @RunWith (NameAwareRunner.class). Sebagai alternatif, Anda bisa meletakkan anotasi itu pada Superclass Test jika Anda tidak ingin mengingatnya setiap waktu. Ini, tentu saja, membatasi pilihan pelari Anda tetapi itu mungkin dapat diterima.

Juga, mungkin perlu sedikit kung fu untuk mendapatkan nama tes saat ini dari Runner dan ke dalam kerangka kerja Anda, tetapi ini setidaknya membuat Anda mendapatkan namanya.

chris.f.jones
sumber
Setidaknya secara konseptual, ide ini agak mudah bagi saya. Maksud saya adalah: Saya tidak akan menyebutnya berbelit-belit.
user98761
"pada superkelas uji ..." - Tolong, tidak ada lagi pola desain berbasis warisan yang mengerikan. Ini sangat JUnit3!
oberlies
3

JUnit 4 tidak memiliki mekanisme out-of-the-box untuk kasus uji untuk mendapatkan namanya sendiri (termasuk selama pengaturan dan teardown).

cordellcp3
sumber
1
Apakah ada mekanisme tidak-out-of-the-box di luar sana selain memeriksa tumpukan?
Dave Ray
4
Bukan kasus yang diberikan jawaban di bawah ini! mungkin memberikan jawaban yang benar kepada orang lain?
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) {
    }
}
Joror
sumber
1
Saya bisa berpendapat bahwa dia hanya ingin menunjukkan solusi .. tidak melihat mengapa suara negatif .... @ downvoter: setidaknya, setidaknya, tambahkan informasi yang berguna ..
Victor
1
@skaffman Kita semua senang melihat berbagai solusi alternatif. Ini adalah yang paling dekat dengan apa yang saya cari: Mendapatkan nama tes tidak langsung di testclass tetapi di kelas yang digunakan selama tes (misalnya di suatu tempat di komponen logger). Di sana, anotasi uji-relevan tidak berfungsi lagi.
Daniel Alder
3

Berdasarkan komentar sebelumnya dan selanjutnya mempertimbangkan saya membuat ekstensi TestWather yang dapat Anda gunakan dalam metode uji JUnit Anda dengan ini:

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

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

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

Kelas pembantu tes adalah sebagai berikut:

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

Nikmati!

Csaba Tenkes
sumber
Hai apa itu ImportUtilsTest, saya mendapatkan kesalahan, sepertinya kelas logger, apakah saya memiliki informasi lebih lanjut? Terima kasih
Sylhare
1
Kelas bernama hanyalah contoh dari kelas uji JUnit: pengguna JUnitHelper. Saya akan memperbaiki contoh penggunaan.
Csaba Tenkes
Ah sekarang aku merasa bodoh, itu sangat jelas. Terima kasih banyak! ;)
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
sumber
0

Saya sarankan Anda memisahkan nama metode pengujian dari kumpulan data pengujian Anda. Saya akan memodelkan kelas DataLoaderFactory yang memuat / cache set data uji dari sumber daya Anda, dan kemudian dalam uji kasus Anda memanggil beberapa metode antarmuka yang mengembalikan satu set data uji untuk kasus uji. Setelah data uji diikat ke nama metode uji, anggap data uji hanya dapat digunakan satu kali, di mana dalam kebanyakan kasus, saya menyarankan agar data uji yang sama digunakan dalam beberapa tes untuk memverifikasi berbagai aspek logika bisnis Anda.

emeraldjava
sumber
0

Anda dapat mencapai ini menggunakan Slf4jdanTestWatcher

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

Di JUnit 5 TestInfobertindak sebagai pengganti drop-in untuk aturan TestName dari JUnit 4.

Dari dokumentasi:

TestInfo digunakan untuk menyuntikkan informasi tentang uji atau wadah saat ini ke dalam metode @Test, @RepeatTest, @ParameterizedTest, @TestFactory, @BeforeEach, @AfterEach, @BeforeAll, dan @AfterAll.

Untuk mengambil nama metode dari tes yang dieksekusi saat ini, Anda memiliki dua opsi: String TestInfo.getDisplayName()dan Method TestInfo.getTestMethod().

Untuk mengambil hanya nama metode pengujian saat ini TestInfo.getDisplayName()mungkin tidak cukup seperti nama tampilan standar metode pengujian methodName(TypeArg1, TypeArg2, ... TypeArg3).
Nama metode duplikat di@DisplayName("..") tidak perlu ide yang bagus.

Sebagai alternatif, Anda bisa menggunakan TestInfo.getTestMethod()yang mengembalikan Optional<Method>objek.
Jika metode pengambilan digunakan di dalam metode pengujian, Anda bahkan tidak perlu menguji nilai yang Optionaldibungkus.

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

JUnit 5 melalui ExtensionContext

Keuntungan:

Anda bisa memiliki fungsi tambahan ExtensionContextdengan mengesampingkan 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();
    }

}
perak
sumber