Bagaimana cara menguji kelas abstrak di Java dengan JUnit?

87

Saya baru mengenal pengujian Java dengan JUnit. Saya harus bekerja dengan Java dan saya ingin menggunakan pengujian unit.

Masalah saya adalah: Saya memiliki kelas abstrak dengan beberapa metode abstrak. Tetapi ada beberapa metode yang tidak abstrak. Bagaimana cara menguji kelas ini dengan JUnit? Kode contoh (sangat sederhana):

abstract class Car {

    public Car(int speed, int fuel) {
        this.speed = speed;
        this.fuel = fuel;
    }

    private int speed;
    private int fuel;

    abstract void drive();

    public int getSpeed() {
        return this.speed;
    }

    public int getFuel() {
        return this.fuel;
    }
}

Saya ingin menguji getSpeed()dan getFuel()berfungsi.

Pertanyaan serupa untuk masalah ini ada di sini , tetapi tidak menggunakan JUnit.

Di bagian FAQ JUnit, saya menemukan tautan ini , tetapi saya tidak mengerti apa yang ingin penulis katakan dengan contoh ini. Apa maksud baris kode ini?

public abstract Source getSource() ;
vasco
sumber
4
Lihat stackoverflow.com/questions/1087339/… untuk dua solusi menggunakan Mockito.
ddso
Apakah ada keuntungan mempelajari kerangka kerja lain untuk pengujian? Apakah Mockito hanya ekstensi untuk jUnit, atau proyek yang sama sekali berbeda?
vasco
Mockito tidak menggantikan JUnit. Seperti kerangka kerja tiruan lainnya, kerangka kerja ini digunakan selain kerangka kerja pengujian unit dan membantu Anda membuat objek tiruan untuk digunakan dalam kasus pengujian Anda.
ddso
1
Bahasa agnostik: stackoverflow.com/questions/243274/…
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Jawaban:

105

Jika Anda tidak memiliki implementasi konkret dari kelas dan metodenya bukan staticapa gunanya mengujinya? Jika Anda memiliki kelas konkret maka Anda akan menguji metode tersebut sebagai bagian dari API publik kelas beton.

Saya tahu apa yang Anda pikirkan "Saya tidak ingin menguji metode ini berulang-ulang, itulah alasan saya membuat kelas abstrak", tetapi argumen balasan saya adalah bahwa tujuan pengujian unit adalah untuk memungkinkan pengembang membuat perubahan, menjalankan tes, dan menganalisis hasilnya. Bagian dari perubahan tersebut dapat mencakup menimpa metode kelas abstrak Anda, protecteddan public, yang dapat mengakibatkan perubahan perilaku mendasar. Bergantung pada sifat perubahan tersebut, hal itu dapat memengaruhi bagaimana aplikasi Anda berjalan dengan cara yang tidak terduga dan mungkin negatif. Jika Anda memiliki masalah suite pengujian unit yang baik yang timbul dari jenis perubahan ini harus terlihat pada waktu pengembangan.

nsfyn55.dll
sumber
17
Cakupan kode 100% adalah mitos. Anda harus memiliki tes yang cukup untuk mencakup semua hipotesis Anda yang diketahui tentang bagaimana aplikasi Anda harus berperilaku (sebaiknya ditulis sebelum Anda menulis kode la Test Driven Development). Saat ini saya sedang mengerjakan tim TDD yang sangat berfungsi dan kami hanya memiliki cakupan 63% pada bangunan terakhir kami yang semuanya ditulis saat kami berkembang. Apa itu bagus? siapa tahu ?, tetapi saya akan menganggap membuang-buang waktu untuk kembali dan mencoba meningkatkannya lebih tinggi.
nsfyn55
3
Tentu. Beberapa orang akan berpendapat bahwa itu adalah pelanggaran TDD yang baik. Bayangkan Anda berada dalam satu tim. Anda membuat asumsi bahwa metode ini final dan tidak melakukan pengujian dalam implementasi konkret apa pun. Seseorang menghapus pengubah dan membuat perubahan yang mempengaruhi seluruh cabang hierarki warisan. Tidakkah Anda ingin rangkaian pengujian Anda menangkapnya?
nsfyn55
31
Saya tidak setuju. Apakah Anda bekerja di TDD atau tidak, metode konkret kelas abstrak Anda berisi kode, oleh karena itu, mereka harus memiliki pengujian (terlepas dari apakah ada subclass atau tidak). Selain itu, pengujian unit dalam kelas tes Java (biasanya). Oleh karena itu, sebenarnya tidak ada logika dalam metode pengujian yang bukan merupakan bagian dari kelas, melainkan kelas supernya. Mengikuti logika tersebut, kita tidak boleh menguji kelas apa pun di Java, kecuali untuk kelas yang tidak memiliki sub kelas sama sekali. Mengenai metode yang diganti, saat itulah Anda menambahkan pengujian untuk memeriksa perubahan / penambahan pada pengujian subkelas.
ethanfar
3
@ nsfyn55 Bagaimana jika metode konkret itu final? Saya tidak melihat alasan untuk menguji metode yang sama beberapa kali jika penerapannya tidak dapat diubah
Dioxin
3
Bukankah kita seharusnya memiliki tes yang menargetkan antarmuka abstrak sehingga kita dapat menjalankannya untuk semua implementasi? Jika tidak memungkinkan, kami akan melanggar Liskov's, yang ingin kami ketahui dan perbaiki. Hanya jika implementasi menambahkan beberapa fungsionalitas yang diperluas (kompatibel), kita harus memiliki pengujian unit khusus untuk itu (dan hanya untuk fungsionalitas tambahan itu).
mentega
36

Buat kelas konkret yang mewarisi kelas abstrak lalu uji fungsi yang diwarisi kelas beton dari kelas abstrak.

Kevin Bowersox
sumber
Apa yang akan Anda lakukan jika Anda memiliki 10 kelas konkret yang memperluas kelas abstrak dan masing-masing kelas konkret ini hanya akan mengimplementasikan 1 metode dan katakanlah 2 metode lainnya sama untuk masing-masing kelas ini, karena diimplementasikan dalam abstrak kelas? Kasus saya adalah saya tidak ingin menyalin-tempel tes untuk kelas abstrak ke setiap subkelas.
scarface
12

Dengan kelas contoh yang Anda posting, tampaknya tidak masuk akal untuk menguji getFuel()dan getSpeed()karena mereka hanya dapat mengembalikan 0 (tidak ada penyetel).

Namun, dengan asumsi bahwa ini hanyalah contoh yang disederhanakan untuk tujuan ilustrasi, dan bahwa Anda memiliki alasan yang sah untuk menguji metode dalam kelas dasar abstrak (orang lain telah menunjukkan implikasinya), Anda dapat mengatur kode pengujian Anda sehingga menciptakan anonim subclass dari kelas dasar yang hanya menyediakan implementasi dummy (tanpa operasi) untuk metode abstrak.

Misalnya, dalam diri Anda, TestCaseAnda dapat melakukan ini:

c = new Car() {
       void drive() { };
   };

Kemudian uji metode lainnya, misalnya:

public class CarTest extends TestCase
{
    private Car c;

    public void setUp()
    {
        c = new Car() {
            void drive() { };
        };
    }

    public void testGetFuel() 
    {
        assertEquals(c.getFuel(), 0);
    }

    [...]
}

(Contoh ini didasarkan pada sintaks JUnit3. Untuk JUnit4, kodenya akan sedikit berbeda, tetapi idenya sama.)

Grodriguez
sumber
Terima kasih atas jawabannya. Ya, contoh saya disederhanakan (dan tidak terlalu bagus). Setelah membaca semua jawaban di sini, saya menulis kelas dummy. Tapi seperti yang ditulis @ nsfyn55 dalam jawabannya, saya menulis tes untuk setiap keturunan kelas abstrak ini.
vasco
9

Jika Anda tetap membutuhkan solusi (misalnya karena Anda memiliki terlalu banyak implementasi dari kelas abstrak dan pengujian akan selalu mengulangi prosedur yang sama) maka Anda dapat membuat kelas pengujian abstrak dengan metode pabrik abstrak yang akan dieksekusi dengan implementasi tersebut kelas tes. Contoh ini berfungsi atau saya dengan TestNG:

Kelas tes abstrak dari Car:

abstract class CarTest {

// the factory method
abstract Car createCar(int speed, int fuel);

// all test methods need to make use of the factory method to create the instance of a car
@Test
public void testGetSpeed() {
    Car car = createCar(33, 44);
    assertEquals(car.getSpeed(), 33);
    ...

Implementasi dari Car

class ElectricCar extends Car {

    private final int batteryCapacity;

    public ElectricCar(int speed, int fuel, int batteryCapacity) {
        super(speed, fuel);
        this.batteryCapacity = batteryCapacity;
    }

    ...

Kelas uji unit ElectricCarTestKelas ElectricCar:

class ElectricCarTest extends CarTest {

    // implementation of the abstract factory method
    Car createCar(int speed, int fuel) {
        return new ElectricCar(speed, fuel, 0);
    }

    // here you cann add specific test methods
    ...
thomas.mc.work
sumber
5

Anda bisa melakukan sesuatu seperti ini

public abstract MyAbstractClass {

    @Autowire
    private MyMock myMock;        

    protected String sayHello() {
            return myMock.getHello() + ", " + getName();
    }

    public abstract String getName();
}

// this is your JUnit test
public class MyAbstractClassTest extends MyAbstractClass {

    @Mock
    private MyMock myMock;

    @InjectMocks
    private MyAbstractClass thiz = this;

    private String myName = null;

    @Override
    public String getName() {
        return myName;
    }

    @Test
    public void testSayHello() {
        myName = "Johnny"
        when(myMock.getHello()).thenReturn("Hello");
        String result = sayHello();
        assertEquals("Hello, Johnny", result);
    }
}
iil
sumber
4

Saya akan membuat kelas dalam jUnit yang mewarisi dari kelas abstrak. Ini dapat dipakai dan memiliki akses ke semua metode yang ditentukan di kelas abstrak.

public class AbstractClassTest {
   public void testMethod() {
   ...
   }
}


class ConcreteClass extends AbstractClass {

}
Marting
sumber
3
Ini adalah nasihat yang sangat bagus. Ini bisa ditingkatkan dengan memberikan contoh. Mungkin contoh kelas yang Anda gambarkan.
SDJMcHattie
2

Anda dapat membuat instance kelas anonim dan kemudian menguji kelas itu.

public class ClassUnderTest_Test {

    private ClassUnderTest classUnderTest;

    private MyDependencyService myDependencyService;

    @Before
    public void setUp() throws Exception {
        this.myDependencyService = new MyDependencyService();
        this.classUnderTest = getInstance();
    }

    private ClassUnderTest getInstance() {
        return new ClassUnderTest() {    
            private ClassUnderTest init(
                    MyDependencyService myDependencyService
            ) {
                this.myDependencyService = myDependencyService;
                return this;
            }

            @Override
            protected void myMethodToTest() {
                return super.myMethodToTest();
            }
        }.init(myDependencyService);
    }
}

Perlu diingat bahwa visibilitas harus protecteduntuk properti myDependencyServicekelas abstrakClassUnderTest .

Anda juga dapat menggabungkan pendekatan ini secara rapi dengan Mockito. Lihat disini .

Samuel
sumber
2

Cara saya menguji ini cukup sederhana, di masing-masing abstractUnitTest.java. Saya hanya membuat kelas di abstractUnitTest.java yang memperluas kelas abstrak. Dan uji seperti itu.

Haomin
sumber
0

Anda tidak dapat menguji seluruh kelas abstrak. Dalam hal ini Anda memiliki metode abstrak, ini berarti metode tersebut harus diimplementasikan oleh kelas yang memperluas kelas abstrak yang diberikan.

Di kelas itu programmer harus menulis kode sumber yang didedikasikan untuk logikanya.

Dengan kata lain, tidak ada gunanya menguji kelas abstrak karena Anda tidak dapat memeriksa perilaku akhirnya.

Jika Anda memiliki fungsionalitas utama yang tidak terkait dengan metode abstrak di beberapa kelas abstrak, buat saja kelas lain di mana metode abstrak akan mengeluarkan beberapa pengecualian.

Damian Leszczyński - Vash
sumber
0

Sebagai opsi, Anda dapat membuat kelas pengujian abstrak yang mencakup logika di dalam kelas abstrak dan memperluasnya untuk setiap pengujian subclass. Sehingga dengan cara ini Anda bisa memastikan logika ini akan diuji untuk setiap anak secara terpisah.

Andrew Taran
sumber