15

For example, if the code generates a random int from 0-10, and takes a different branch on each outcome, how can one design a test suite to guarantee 100% statement coverage in such code?

In Java, the code might be something like:

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

5 Answers 5

22

Expanding David's answer whom I totally agree with that you should create a wrapper for Random. I wrote pretty much the same answer about it earlier in a similar question so here is a "Cliff's notes version" of it.

What you should do is to first create the wrapper as an interface (or abstract class):

public interface IRandomWrapper {
    int getInt();
}

And the concrete class for this would look like this:

public RandomWrapper implements IRandomWrapper {

    private Random random;

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

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

}

Say your class is the following:

class MyClass {

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

}

In order to use the IRandomWrapper correctly you need to modify your class to take it as a member (through constructor or a 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
        }
    }

}

You can now test your class's behaviour with the wrapper, by mocking the wrapper. You can do this with a mocking framework, but this is easy to do by yourself as well:

public class MockedRandomWrapper implements IRandomWrapper {

   private int theInt;    

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

   public int getInt() { 
       return theInt;
   }

}

Since your class expects something that looks like an IRandomWrapper you can now use the mocked one to force the behaviour in your test. Here are some examples of JUnit tests:

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

Hope this helps.

2
  • 3
    Totally agree with this. You test a random event by removing the random nature of the event. The same theory can be used for timestamps
    – Richard
    Nov 3, 2011 at 12:33
  • 3
    Note: this tehcnique, of giving an object the other object it needs, instead of letting him instanciate them, is called Dependency Injection Nov 3, 2011 at 15:19
23

You can (should) wrap the random generation code in a class or method and then mock/override it during tests to set the value you want, so that your tests are predictable.

5

You've got a specified range (0-10), and a specified granularity (whole numbers). So when testing, you don't test with the random numbers. You test within a loop that hits each case in turn. I'd advise passing the random number into a sub-function containing the case statement, which allows you to just test the sub-function.

2
  • much better (because more simple) than what I suggested, wish I could transfer my upvotes :)
    – David
    Nov 3, 2011 at 14:08
  • Actually you should do both. Test with a mock RandomObject to test each branch individually, and test repeatedly with real RandomObject. The former is a unit test, the latter more like an integrationt test.
    – sleske
    Nov 3, 2011 at 15:22
3

You can use PowerMock library to mock the Random class and stub its nextInt() method to return the expected value. No need to change your original code if you don't want to.

I'm using PowerMockito and just tested a method similar to yours. For the code that you posted the JUnit test should look something like this:

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

    (...)

You can also stub the nextInt(int) call to receive any parameter, in case you want to add more cases at your switch:

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

Pretty, isn't it? :)

2

Use QuickCheck! I just started playing with this recently and it is amazing. Like most cool ideas it comes from Haskell but the basic idea is that instead of giving your test pre canned test cases you let your random number generator build them for you. That way instead of the 4-6 cases that you would probably come up with in xUnit you can have the computer try hundreds or thousands of inputs and see which ones don't conform to the rules you have set.

Also QuickCheck will when it finds a failing case try to simplify it so that it can find the simplest possible case that does fail. (And of course when you do find a failing case you can then build it into a xUnit test as well)

There appear to be at least two versions for Java so that part should not be a problem.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.