Как я могу разработать контрольные примеры для покрытия кода на основе случайных событий?

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
}

Надеюсь это поможет.

Spoike
источник
3
Полностью с этим согласен. Вы проверяете случайное событие, удаляя случайный характер события. Та же теория может быть использована для отметок времени
Ричард
3
Примечание: этот метод предоставления объекту другого объекта, в котором он нуждается, вместо того, чтобы позволить ему создавать его экземпляры, называется
инъекцией
23

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

Дэвид
источник
5

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

deworde
источник
гораздо лучше (потому что проще), чем то, что я предложил, если бы я мог передать свои голоса :)
Дэвид
На самом деле вы должны сделать оба. Протестируйте с помощью ложного объекта RandomObject, чтобы проверить каждую ветку отдельно, и несколько раз протестируйте с реальным объектом RandomObject. Первый является модульным тестом, второй больше похож на интеграционный тест.
слеске
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());

Довольно, не правда ли? :)

LizardCZ
источник
2

Используйте QuickCheck ! Я только начал играть с этим недавно, и это удивительно. Как и большинство классных идей, оно исходит от Haskell, но основная идея заключается в том, что вместо того, чтобы давать тесты заранее подготовленным тестам, вы позволяете генератору случайных чисел создавать их для вас. Таким образом, вместо 4-6 случаев, которые вы, вероятно, придумали в xUnit, вы можете заставить компьютер попробовать сотни или тысячи входов и посмотреть, какие из них не соответствуют установленным вами правилам.

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

Похоже, что для Java существует как минимум две версии, поэтому эта часть не должна быть проблемой.

Захари К
источник