如何设计测试用例以覆盖基于随机事件的代码?


15

例如,如果代码生成一个从0到10的随机整数,并且在每个结果上采用不同的分支,那么如何设计一种测试套件以保证此类代码中100%的语句覆盖率?

在Java中,代码可能类似于:

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

Answers:


22

扩大大卫的回答就是我完全同意你应该创建随机的包装一致。之前,我在一个类似的问题就此问题写了几乎相同的答案,因此这里是它的“悬崖笔记版本”。

您应该做的是首先将包装器创建为接口(或抽象类):

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,您需要修改类以使其成为成员(通过构造函数或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
        }
    }

}

现在,您可以通过模拟包装器来使用包装器测试类的行为。您可以使用模拟框架来执行此操作,但这也很容易自己完成:

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
完全同意这一点。您可以通过删除事件的随机性来测试随机事件。时间戳可以使用相同的理论
理查德(Richard)

3
注意:这种给对象提供所需的其他对象而不是让其实例化的技术称为“依赖注入
Clement Herreman”,

23

您可以(应该)将随机生成的代码包装在类或方法中,然后在测试过程中对其进行模拟/覆盖以设置所需的值,从而使测试是可预测的。


5

您具有指定的范围(0-10)和指定的粒度(整数)。因此,在测试时,您无需使用随机数进行测试。您在一个循环中进行测试,该循环依次击中每种情况。我建议将随机数传递到包含case语句的子函数中,该子函数允许您仅测试该子函数。


比我建议的要好得多(因为更简单),希望我能转让我的投票:)
David

实际上,您应该两者都做。使用模拟的RandomObject进行测试以分别测试每个分支,然后使用真实的RandomObject进行重复测试。前者是单元测试,后者更像是集成测试。
sleske 2011年

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,但基本思想是让随机数生成器为您构建它们,而不是给您预先安装的测试用例。这样,您可以让计算机尝试数百或数千个输入,然后查看哪些不符合您设置的规则,而不是xUnit中可能出现的4-6种情况。

同样,QuickCheck在发现失败的案例时会尝试简化它,以便它可以找到最简单的失败案例。(当然,当您发现失败的案例时,也可以将其构建到xUnit测试中)

Java似乎至少有两个版本,因此该部分应该没有问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.