Як я можу спроектувати тестові випадки для покриття коду на основі випадкових подій?


15

Наприклад, якщо код генерує випадковий int від 0-10 і займає іншу гілку за кожним результатом, як можна створити тестовий набір, щоб гарантувати 100% охоплення оператора в такому коді?

У Java код може бути приблизно таким:

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

Відповіді:


22

Розширення відповіді Девіда, з яким я повністю згоден, що ви повинні створити обгортку для Random. Я написав майже таку ж відповідь про це раніше в подібному питанні, тому ось "версія приміток Кліффа" його.

Що потрібно зробити, це спершу створити обгортку як інтерфейс (або абстрактний клас):

public interface IRandomWrapper {
    int getInt();
}

І конкретний клас для цього виглядав би так:

public RandomWrapper implements IRandomWrapper {

    private Random random;

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

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

}

Скажіть, що ваш клас такий:

class MyClass {

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

}

Щоб правильно використовувати IRandomWrapper, вам потрібно змінити свій клас, щоб прийняти його як член (через конструктор або сеттер):

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

}

Тепер ви можете перевірити поведінку свого класу за допомогою обгортки, глузуючи з обгортки. Це можна зробити з глузуючої рамки, але це також легко зробити самостійно:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

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

   public int getInt() { 
       return theInt;
   }

}

Оскільки ваш клас очікує чогось схожого на IRandomWrapperтепер, ви можете використовувати насмішкувату, щоб змусити поведінку у вашому тесті. Ось кілька прикладів тестів 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
}

Сподіваюсь, це допомагає.


3
Повністю згоден з цим. Ви перевіряєте випадкову подію, видаляючи випадковий характер події. Ця ж теорія може бути використана і для часових позначок
Річард

3
Примітка: ця техніка надання об'єкту іншого необхідного йому об'єкта, замість того, щоб давати йому імпульсувати їх, називається
ін'єкцією

23

Ви можете (слід) загортати довільний код генерації в клас або метод, а потім знущатися / змінювати його під час тестів, щоб встановити потрібне значення, щоб ваші тести були передбачуваними.


5

Ви отримали заданий діапазон (0-10) та задану деталізацію (цілі числа). Тож під час тестування ви не тестуєте з випадковими числами. Ви тестуєте в циклі, який по черзі потрапляє на кожен випадок. Я б радив передати випадкове число в підфункцію, що містить випадок регістру, який дозволяє просто перевірити підфункцію.


набагато краще (тому що простіше), ніж те, що я запропонував, бажаю, щоб я міг передати свої результати :)
Девід

Насправді ви повинні робити і те, і інше. Тестуйте з макетом RandomObject, щоб перевірити кожну гілку окремо, і повторно тестуйте за допомогою справжнього RandomObject. Перший є одиничним тестом, останній більше нагадує інтеграційний тест.
sleske

3

Ви можете використовувати бібліотеку PowerMock для знущання з класу Random та заглушення методу nextInt () для повернення очікуваного значення. Не потрібно змінювати свій вихідний код, якщо ви цього не хочете.

Я використовую PowerMockito і тільки що випробував метод, подібний до вашого. Для коду, який ви опублікували тест JUnit, повинен виглядати приблизно так:

@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
     }

    (...)

Ви також можете заглушити виклик nextInt (int), щоб отримати будь-який параметр, якщо ви хочете додати більше випадків на своєму комутаторі:

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

Гарненько, чи не так? :)


2

Використовуйте QuickCheck ! Я нещодавно почав грати з цим нещодавно, і це дивно. Як і більшість класних ідей, вона походить від Haskell, але основна ідея полягає в тому, що замість того, щоб давати свої тестові попередньо консервовані тестові випадки, ви дозволяєте вашому генератору випадкових чисел створити їх для вас. Таким чином, замість 4-6 випадків, які ви, мабуть, зібрали в xUnit, ви можете змусити комп'ютер спробувати сотні чи тисячі входів і побачити, які з них не відповідають правилам, які ви встановили.

Також QuickCheck, коли виявить невдалий випадок, спробує спростити його, щоб він міг знайти найпростіший можливий випадок, який не вдається. (І звичайно, коли ви знайдете невдалий випадок, тоді можете також вбудувати його в тест xUnit)

Здається, що для Java існує щонайменше дві версії, тому ця частина не повинна бути проблемою.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.