Bagaimana saya bisa merancang kasus uji untuk mencakup kode berdasarkan peristiwa acak?

15

Sebagai contoh, jika kode menghasilkan int acak dari 0-10, dan mengambil cabang berbeda pada setiap hasil, bagaimana seseorang dapat merancang suite uji untuk menjamin cakupan pernyataan 100% dalam kode tersebut?

Di Jawa, kodenya mungkin seperti:

int i = new Random().nextInt(10);
switch(i)
{
    //11 case statements
}
Midhat
sumber

Jawaban:

22

Memperluas jawaban David yang sepenuhnya saya setujui bahwa Anda harus membuat pembungkus untuk Acak. Saya menulis jawaban yang hampir sama tentang hal itu sebelumnya dalam pertanyaan serupa jadi di sini adalah "versi catatan Cliff" itu.

Yang harus Anda lakukan adalah terlebih dahulu membuat wrapper sebagai antarmuka (atau kelas abstrak):

public interface IRandomWrapper {
    int getInt();
}

Dan kelas konkret untuk ini akan terlihat seperti ini:

public RandomWrapper implements IRandomWrapper {

    private Random random;

    public RandomWrapper() {
        random = new Random();
    }

    public int getInt() {
        return random.nextInt(10);
    }

}

Katakanlah kelas Anda adalah sebagai berikut:

class MyClass {

    public void doSomething() {
        int i=new Random().nextInt(10)
        switch(i)
        {
            //11 case statements
        }
    }

}

Untuk menggunakan IRandomWrapper dengan benar, Anda perlu memodifikasi kelas untuk menjadikannya sebagai anggota (melalui konstruktor atau setter):

public class MyClass {

    private IRandomWrapper random = new RandomWrapper(); // default implementation

    public setRandomWrapper(IRandomWrapper random) {
        this.random = random;
    }

    public void doSomething() {
        int i = random.getInt();
        switch(i)
        {
            //11 case statements
        }
    }

}

Anda sekarang dapat menguji perilaku kelas Anda dengan pembungkus, dengan mengejek pembungkus. Anda bisa melakukan ini dengan kerangka mengejek, tetapi ini juga mudah dilakukan sendiri:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

   public MockedRandomWrapper(int theInt) {
       this.theInt = theInt;
   }

   public int getInt() { 
       return theInt;
   }

}

Karena kelas Anda mengharapkan sesuatu yang terlihat seperti IRandomWrapperAnda sekarang dapat menggunakan yang diejek untuk memaksa perilaku dalam tes Anda. Berikut adalah beberapa contoh tes JUnit:

@Test
public void testFirstSwitchStatement() {
    MyClass mc = new MyClass();
    IRandomWrapper random = new MockedRandomWrapper(0);
    mc.setRandomWrapper(random);

    mc.doSomething();

    // verify the behaviour for when random spits out zero
}

@Test
public void testFirstSwitchStatement() {
    MyClass mc = new MyClass();
    IRandomWrapper random = new MockedRandomWrapper(1);
    mc.setRandomWrapper(random);

    mc.doSomething();

    // verify the behaviour for when random spits out one
}

Semoga ini membantu.

Spoike
sumber
3
Sepenuhnya setuju dengan ini. Anda menguji acara acak dengan menghapus sifat acak acara tersebut. Teori yang sama dapat digunakan untuk cap waktu
Richard
3
Catatan: teknik ini, untuk memberikan objek objek lain yang dibutuhkannya, alih-alih membiarkannya memberi mereka instanciate, disebut Dependency Injection
Clement Herreman
23

Anda dapat (harus) membungkus kode generasi acak dalam kelas atau metode dan kemudian mengejek / menimpanya selama tes untuk menetapkan nilai yang Anda inginkan, sehingga tes Anda dapat diprediksi.

David
sumber
5

Anda memiliki rentang tertentu (0-10), dan granularity tertentu (bilangan bulat). Jadi saat pengujian, Anda tidak menguji dengan angka acak. Anda menguji dalam satu lingkaran yang mengenai setiap kasus secara bergantian. Saya menyarankan untuk memberikan nomor acak ke dalam sub-fungsi yang berisi pernyataan kasus, yang memungkinkan Anda untuk hanya menguji sub-fungsi.

deworde
sumber
jauh lebih baik (karena lebih sederhana) daripada apa yang saya sarankan, berharap saya dapat mentransfer upvotes saya :)
David
Sebenarnya Anda harus melakukan keduanya. Uji dengan RandomObject tiruan untuk menguji setiap cabang secara individual, dan uji berulang kali dengan RandomObject nyata. Yang pertama adalah tes unit, yang terakhir lebih seperti tes integrasi.
sleske
3

Anda bisa menggunakan pustaka PowerMock untuk mengejek kelas acak dan mematikan metode nextInt () untuk mengembalikan nilai yang diharapkan. Tidak perlu mengubah kode asli Anda jika Anda tidak mau.

Saya menggunakan PowerMockito dan baru saja menguji metode yang mirip dengan Anda. Untuk kode yang Anda posting tes JUnit akan terlihat seperti ini:

@RunWith(PowerMockRunner.class)
@PrepareForTest( { Random.class, ClassUsingRandom.class } ) // Don't forget to prepare the Random class! :)

public void ClassUsingRandomTest() {

    ClassUsingRandom cur;
    Random mockedRandom;

    @Before
    public void setUp() throws Exception {

        mockedRandom = PowerMockito.mock(Random.class);

        // Replaces the construction of the Random instance in your code with the mock.
        PowerMockito.whenNew(Random.class).withNoArguments().thenReturn(mockedRandom);

        cur = new ClassUsingRandom();
    }

    @Test
    public void testSwitchAtZero() {

        PowerMockito.doReturn(0).when(mockedRandom).nextInt(10);

        cur.doSomething();

        // Verify behaviour at case 0
     }

    @Test
    public void testSwitchAtOne() {

        PowerMockito.doReturn(1).when(mockedRandom).nextInt(10);

        cur.doSomething();

        // Verify behaviour at case 1
     }

    (...)

Anda juga dapat mematikan panggilanInt (int) berikutnya untuk menerima parameter apa pun, jika Anda ingin menambahkan lebih banyak kasus di saklar Anda:

PowerMockito.doReturn(0).when(mockedRandom).nextInt(Mockito.anyInt());

Cantik, bukan? :)

LizardCZ
sumber
2

Gunakan QuickCheck ! Saya baru saja mulai bermain dengan ini baru-baru ini dan itu luar biasa. Seperti kebanyakan ide keren, itu berasal dari Haskell tetapi ide dasarnya adalah bahwa alih-alih memberikan uji pra-uji kaleng Anda, Anda membiarkan generator angka acak Anda membuatnya untuk Anda. Dengan begitu, alih-alih dari 4-6 case yang mungkin Anda buat di xUnit Anda dapat meminta komputer mencoba ratusan atau ribuan input dan melihat mana yang tidak sesuai dengan aturan yang telah Anda tetapkan.

QuickCheck juga akan menemukan kasus yang gagal mencoba menyederhanakannya sehingga dapat menemukan kasus paling sederhana yang gagal. (Dan tentu saja ketika Anda menemukan case yang gagal, Anda dapat membangunnya menjadi tes xUnit juga)

Tampaknya ada setidaknya dua versi untuk Java sehingga bagian itu seharusnya tidak menjadi masalah.

Zachary K
sumber