Testng: @Test metode dari kelas super dieksekusi ulang untuk setiap sub kelas

Dibuat pada 10 Jun 2019  ·  12Komentar  ·  Sumber: cbeust/testng

TestNG Versi 6.14.3

Perilaku yang diharapkan

Metode @Test dari kelas super dieksekusi sekali tidak peduli berapa banyak sub kelas yang mewarisi kelas super.

Perilaku sebenarnya

Metode @Test dieksekusi dengan setiap sub kelas.

Apakah masalah dapat direproduksi pada runner?

  • [ ] Kerang
  • [ ] Maven
  • [x] Gradle
  • [ ] Semut
  • [ ] Gerhana
  • [ ] IntelliJ
  • [ ] NetBeans

Contoh kasus uji

public class BaseTest  extends SetupClass{
    @BeforeClass...
    @BeforeMethod...
    @AfterClass...
    @AfterMethod....

    <strong i="22">@Test</strong>
    public void baseMethod(){
        System.out.println("base method");
    }
}
public class Test1 extends BaseTest{
    <strong i="25">@Test</strong>
    public void test1(){
        System.out.println("test1");
    }
}
public class Test2 extends BaseTest{
    <strong i="28">@Test</strong>
    public void test2(){
        System.out.println("test2");
    }
}

Saya menjalankan tes ini secara paralel dari file xml TestNG yang terlihat seperti ini:

<suite name="Regression Suite" verbose="1" parallel="classes" thread-count="4">
    <parameter name="project" value="TestPrj"/>
    <parameter name="server" value="QA01"/>
    <test name="QA01 TestPrj">
        <parameter name="environment" value="macos"/>
        <groups>
            <run>
                <include name="regression"/>
            </run>
        </groups>
        <packages>
            <package name="test"/>
        </packages>
    </test>
</suite>

PS Mengedit kode dalam masalah github sangat buruk ... silakan pindah ke yang lain.

inheritance

Semua 12 komentar

@vlad230

PS Mengedit kode dalam masalah github sangat buruk ... silakan pindah ke yang lain.

Anda mungkin ingin meluangkan waktu untuk membiasakan diri dengan penurunan harga. Itu akan membuatnya sangat mudah untuk menambahkan cuplikan kode bersama dengan pemformatan.

Saya tidak yakin saya memahami masalah Anda di sini. Pada akhirnya kelas anak akan mendapatkan metode kelas dasar juga. Dan jika mereka dianotasi dengan anotasi TestNG, mereka akan dieksekusi untuk setiap kelas anak. Itu adalah perilaku TestNG. Jadi saya akan mengatakan bahwa TestNG berfungsi seperti yang dirancang.

@juherr - Pikiran Anda?

@krmahadevan terima kasih atas formatnya :)

Saya tidak yakin saya memahami masalah Anda di sini. Pada akhirnya kelas anak akan mendapatkan metode kelas dasar juga. Dan jika mereka dianotasi dengan anotasi TestNG, mereka akan dieksekusi untuk setiap kelas anak. Itu adalah perilaku TestNG. Jadi saya akan mengatakan bahwa TestNG berfungsi seperti yang dirancang.

Jika Anda bertanya kepada saya, tidak masuk akal untuk menjalankan tes di kelas super beberapa kali.
Jika ini adalah perilaku yang diharapkan dari TestNG saat ini, mungkin kami dapat menambahkan ini sebagai peningkatan.

Bekerja seperti yang dirancang.
Mengapa Anda tidak memindahkan tes baseMethod di kelasnya sendiri?

@vlad230

Jika Anda bertanya kepada saya, tidak masuk akal untuk menjalankan tes di kelas super beberapa kali.

Apa sebenarnya yang ingin Anda capai? Akan sangat bagus jika Anda bisa membantu menguraikan atau menjelaskan skenario Anda.

Jika ini adalah perilaku yang diharapkan dari TestNG saat ini, mungkin kami dapat menambahkan ini sebagai peningkatan.

Saya tidak yakin itu akan dianggap sebagai peningkatan karena kontra intuitifnya. Jika pada dasarnya Anda ingin metode kelas dasar dieksekusi tepat satu kali, tidak peduli berapa banyak kelas anak yang ada, maka Anda dapat melakukannya hari ini di proyek pengujian Anda dengan melakukan hal berikut

  1. Minta kelas dasar Anda mengimplementasikan org.testng.IHookable
  2. Di dalam metode run() , tambahkan pemeriksaan edit yang akan memeriksa apakah metode kelas dasar sudah dijalankan dan jalankan jika tidak dijalankan.

Berikut adalah sampelnya

Antarmuka penanda yang menunjukkan bahwa metode kelas dasar harus dijalankan hanya sekali

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD, TYPE})
public <strong i="20">@interface</strong> RunOnce {
}

Inilah lajang pelacak negara

import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class StateTracker {

  private static final StateTracker instance = new StateTracker();

  private final Map<String, Object> runOnceData = new ConcurrentHashMap<>();

  private StateTracker() {}

  public static StateTracker getInstance() {
    return instance;
  }

  boolean canExecute(Method method) {
    RunOnce runOnce = method.getAnnotation(RunOnce.class);
    String methodName = method.getName();
    boolean execute = true;
    if (runOnce != null) {
      if (runOnceData.containsKey(methodName)) {
        execute = false;
      } else {
        runOnceData.put(methodName, new Object());
      }
    }
    return execute;
  }
}

Begini tampilan kelas dasarnya

import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.annotations.Test;

public class BaseTest implements IHookable {

  <strong i="27">@Test</strong>
  <strong i="28">@RunOnce</strong>
  public void baseclassTestMethod() {
    System.err.println("Base class test method executed");
  }

  <strong i="29">@Override</strong>
  public void run(IHookCallBack callBack, ITestResult testResult) {
    boolean execute =
        StateTracker.getInstance()
            .canExecute(testResult.getMethod().getConstructorOrMethod().getMethod());
    if (execute) {
      callBack.runTestMethod(testResult);
    } else {
      testResult.setStatus(ITestResult.SKIP);
      testResult.setThrowable(new RuntimeException("Intentionally skipping"));
    }
  }
}

Begini tampilan kelas anak

import org.testng.annotations.Test;

public class ChildClass1 extends BaseTest {

  <strong i="33">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}
import org.testng.annotations.Test;

public class ChildClass2 extends BaseTest {

  <strong i="36">@Test</strong>
  public void childTest() {
    System.err.println(getClass().getName() + ".childTest() executed");
  }
}

Anda perlu menambahkan beberapa mekanisme pemfilteran yang memangkas hasil tes dan menghapus tes yang dilewati yang dianotasi menggunakan @RunOnce

@krmahadevan
Apa sebenarnya yang ingin Anda capai? Akan sangat bagus jika Anda bisa membantu menguraikan atau menjelaskan skenario Anda.

Saya mencoba memisahkan tes di beberapa kelas (yang terkait dengan fitur). Misalnya Anda memiliki "kelas dasar" yang memiliki bagian setup untuk fitur yang dengan @Before & @After metode sepanjang sisi metode pembantu lainnya (tidak tes) yang mendukung tes dari kelas dasar sepanjang sisi tes di "kelas anak ". Jadi, dengan cara ini Anda hanya akan menambahkan tes baru ke kelas anak Anda dan tidak menduplikasi kode di kelas anak.
Kelas dasar akan memiliki beberapa metode pengujian yang menguji bagian yang lebih umum dari fitur itu dan kelas anak akan memiliki pengujian yang lebih spesifik.
Karena saya mencoba mengoptimalkan waktu uji coba dengan menjalankan kelas secara paralel, saya harus membagi "kelas dasar" asli saya yang memiliki 50+ metode pengujian menjadi beberapa "kelas anak" yang lebih kecil sehingga kelas yang lebih kecil ini didistribusikan ke utas lain / pelaksana.

@krmahadevan terima kasih banyak atas solusi terperincinya tetapi bagi saya itu terlihat agak rumit. Karena TestNG dapat dikustomisasi, saya lebih suka melihat ini sebagai perilaku default.
Saya tidak melihat kebutuhan untuk terus-menerus menjalankan kembali tes dari kelas dasar. Jika ini masalah penyiapan/teardown, seseorang selalu dapat menggunakan @Sebelum/ @Setelah itu akan dieksekusi ulang setiap kali sebelum kelas anak dari kelas dasar.

Ya, saya pikir perilakunya bisa dikonfigurasi.

Faktanya, saya tidak menemukan kasus penggunaan di mana kami ingin menjalankan tes induk untuk setiap instance anak.
Kecuali jika Anda memiliki ketergantungan langsung untuk diuji ( dependsOnMethods ) karena status pengujian sering dimodifikasi seperti itu dalam pengujian integrasi.

@krmahadevan WDYT?

@vlad230

terima kasih banyak atas solusi terperincinya tetapi bagi saya itu terlihat agak rumit

Kasus penggunaan Anda juga terlihat sedikit berbeda dari biasanya. Selain itu, saya telah memberi Anda contoh kerja tentang bagaimana hal itu dapat dilakukan. Yang perlu Anda lakukan hanyalah mengadopsi ini dan membangunnya.

Karena TestNG dapat dikustomisasi, saya lebih suka melihat ini sebagai perilaku default.

Tidak. Itu tidak bisa dilakukan menurut saya. Tes dimaksudkan untuk dieksekusi sepanjang waktu. Hanya karena mereka berada di kelas dasar tidak berarti mereka harus dieksekusi secara selektif. TestNG dapat disesuaikan. Tapi itu tidak berarti TestNG perlu mendukung semua penyesuaian. Itu hanya perlu menyediakan cara di mana pengguna harus dapat menyesuaikannya dengan kebutuhan mereka dan bekerja dengannya. Contoh yang saya bagikan adalah contoh penerapan penyesuaian tersebut tanpa TestNG harus mengalami perubahan.

Saya tidak melihat kebutuhan untuk terus-menerus menjalankan kembali tes dari kelas dasar. Jika ini masalah penyiapan/teardown, seseorang selalu dapat menggunakan @Sebelum/ @setelah itu akan dieksekusi ulang setiap kali sebelum kelas anak dari kelas dasar.

Bagi saya itu hanyalah salah satu cara memandang sesuatu. Jika metode tertentu tidak harus dijalankan setiap saat, maka Anda tidak boleh menggunakan anotasi @Test . Anda dapat menggunakan anotasi konfigurasi seperti @BeforeTest (yang dijalankan hanya sekali per <test> tag) atau @BeforeSuite (yang dijalankan hanya sekali per <suite> tag) bukan? Bukankah mereka bekerja untuk Anda.

Saya tidak yakin, apakah metode pengujian Anda di kelas dasar Anda benar-benar metode pengujian. Bagi saya mereka terdengar lebih seperti konfigurasi yang menetapkan kondisi penyiapan yang harus dipenuhi agar pengujian dapat dijalankan.

@juherr - Saya tidak yakin saya setuju dengan kasus penggunaan di sini. Sebuah tes terlepas dari mana ditemukan dimaksudkan untuk dieksekusi. Jika ada skenario di mana kita tidak ingin itu dieksekusi untuk semua kelas anak, maka kita harus menggunakan anotasi konfigurasi dan bukan anotasi @Test .

Di sisi implementasi, saya pikir ini akan menambah banyak kekacauan pada basis kode terutama ketika orang mulai menggunakan grup sebagai sarana untuk eksekusi.

Selain itu, saya merasa sudah ada cara untuk melakukan ini saat ini di dalam TestNG (yang telah saya bagikan sebagai contoh). Mengapa tidak memanfaatkan itu saja? Ini tampaknya merupakan kasus penggunaan satu kali, dan sepertinya tidak cocok dengan cara biasa TestNG digunakan.

@krmahadevan Maaf, tapi saya pikir Anda tidak mengerti apa yang saya coba lakukan.
Di kelas induk saya (BaseTest - misalnya DashboardTest) saya memiliki 15 tes sekarang dan saya memiliki 3 kelas anak (misalnya DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) yang masing-masing memiliki tes di dalamnya. Menggunakan pendekatan yang saya jelaskan di posting saya, setiap kali saya menjalankan tes, 15 tes tersebut dieksekusi 3 kali lipat (jadi, 3x15 = 45 tes sedang dieksekusi) tidak membawa nilai dan memperpanjang waktu proses dengan sia-sia, bukan hanya menjalankan 15 tes sekali.

Saya masih berpikir tidak masuk akal untuk tes yang terletak di kelas induk untuk dieksekusi ulang setiap kali tes dari kelas anak dieksekusi. Lebih suka perilaku default TestNG untuk mengeksekusi metode pengujian dari kelas induk satu kali, terlepas dari jumlah kelas anak yang memperluasnya.

Jika ini tidak bisa dilakukan, saya mengerti. Terima kasih.

@vlad230

Terima kasih telah menambahkan konteks tambahan.

Di kelas induk saya (BaseTest - misalnya DashboardTest) saya memiliki 15 tes sekarang dan saya memiliki 3 kelas anak (misalnya DashboardFilterTest, DashboardTreeTest, DashboardEditorTest) yang masing-masing memiliki tes di dalamnya. Menggunakan pendekatan yang saya jelaskan di posting saya, setiap kali saya menjalankan tes, 15 tes tersebut dieksekusi 3 kali lipat (jadi, 3x15 = 45 tes sedang dieksekusi) tidak membawa nilai dan memperpanjang waktu proses dengan sia-sia, bukan hanya menjalankan 15 tes sekali.

Dalam hal ini, mengapa DashboardFilterTest, DashboardTreeTest, DashboardEditorTest diperpanjang DashboardTest ? Sepertinya tes DashboardTest perlu berada di kelas mereka sendiri alih-alih Anda memanfaatkan pendekatan pewarisan di sini bukan? Kelas dasar harus difaktorkan ulang untuk menghapus semua metode pengujian umum dan ditempatkan di kelas pengujian terpisah, dan DashboardTest harus difaktorkan ulang untuk hanya menampung metode umum non @Test bukan?

Jika ini tidak bisa dilakukan, saya mengerti. Terima kasih.

Ini bukan tentang apakah itu bisa dilakukan atau tidak (tantangan implementasi adalah topik yang terpisah), tetapi saya masih kesulitan untuk memahami validitas dari use case itu sendiri.

@krmahadevan

Dalam hal ini, mengapa DashboardFilterTest, DashboardTreeTest, DashboardEditorTest diperpanjang DashboardTest ? Sepertinya tes DashboardTest perlu berada di kelas mereka sendiri alih-alih Anda memanfaatkan pendekatan pewarisan di sini bukan? Kelas dasar harus difaktorkan ulang untuk menghapus semua metode pengujian umum dan ditempatkan di kelas pengujian terpisah, dan DashboardTest harus difaktorkan ulang untuk hanya menampung metode umum non @Test bukan?

'DashboardTest' berisi metode pembantu (misalnya openDashboard(), createDashboard() dll.) dan metode setup/teardown/cleanup (@Before/@After) yang juga diperlukan untuk kelas anak. Jadi, saya tidak perlu mereplikasi kode di kelas anak.
Ya, beberapa tes di 'DashboardTest' dapat dipindahkan ke beberapa kelas khusus lainnya, tetapi saya ingin menyimpannya di sini karena lebih umum (misalnya memeriksa tampilan keseluruhan dasbor, tes CRUD dasar, atau bahkan beberapa tes yang tidak' t cocok dalam kategori tertentu atau campuran).

Tentu, saya dapat memiliki kelas tes non-@Test (walaupun itu akan aneh dan seseorang dapat menambahkan tes di sini karena kesalahan) tetapi itu hanya untuk menghindari masalah ini, bukan?

@vlad230

'DashboardTest' berisi metode pembantu (misalnya openDashboard(), createDashboard() dll.) dan metode setup/teardown/cleanup (@Before/@after) yang juga diperlukan untuk kelas anak. Jadi, saya tidak perlu mereplikasi kode di kelas anak.

Ini masuk akal. dan ya, mereka dapat berada di dalam kelas dasar dengan baik sehingga metode konfigurasi juga tersedia untuk kelas anak.

Ya, beberapa tes di 'DashboardTest' dapat dipindahkan ke beberapa kelas khusus lainnya, tetapi saya ingin menyimpannya di sini karena lebih umum (misalnya memeriksa tampilan keseluruhan dasbor, tes CRUD dasar, atau bahkan beberapa tes yang tidak' t cocok dalam kategori tertentu atau campuran).

Anda masih bisa membuat kelas bernama GenericDashboardTest extends DashboardTest yang menampung semua metode generik @Test di dalamnya.

Tentu, saya dapat memiliki kelas tes non-@test (walaupun akan aneh dan seseorang dapat menambahkan tes di sini karena kesalahan) tetapi itu hanya untuk menghindari masalah ini, bukan?

Nah, bukankah itu gunanya review kode :) Untuk mencegah slippage seperti itu. Kelas dasar (dari apa yang Anda jelaskan) tidak perlu berisi metode umum @Test generik. Itu dapat dengan baik menampung hanya pembantu dan konfigurasi dan Anda dapat memiliki satu kelas pengujian lagi yang memperluas kelas dasar dan menampung metode pengujian generik. Itu harus menyelesaikan kebingungan yang ada.

Menutup masalah ini dengan resolusi sebagai _berfungsi seperti yang dirancang_

Apakah halaman ini membantu?
0 / 5 - 0 peringkat