2

I have a method which is a part of the interface I am implementing. This method calls another private method which uses a random number generator to produce output and returns it to the calling method. I want to test the calling method. How can I do that? This is the method under test:

 @Override
 public String generate(int wordCount) {
    StringBuilder sentence = new StringBuilder();

    List<String> selectedStrings = selectRandomStringsFromInternalVocabulary(wordCount, new Random());
    selectedStrings.sort(Comparator.<String>naturalOrder());

    swapOddIndexedStringsWithEvenIndexedStrings(selectedStrings);

    for (String word: selectedStrings)
        sentence.append(word)
                .append(" ");


    return sentence.toString().trim();
}

This is the method that uses random number generator:

private List<String> selectRandomStringsFromInternalVocabulary(int wordCount, Random random) {
    List<String> selectedStrings = new ArrayList<>();
    int wordCountInVocabulary = internalVocabulary.size();

    while (wordCount-- != 0) {
        int stringIndex = random.nextInt(wordCountInVocabulary);
        selectedStrings.add(internalVocabulary.get(stringIndex));
    }

    return selectedStrings;
}

There are a few things that I've thought I can do: 1. Make the second method package-private and test it. But I don't want to test a private method if I can avoid it. 2. Add Random as a parameter to the calling function and pass a mock during test. However, its part of the interface and other classes implementing it does not use RNG. Furthermore, I don't want clients to know about the implementation details.

I have gone through these questions: 1. Unit testing methods with indeterminate output 2. Unit Testing a function with random behavior

But the suggestions are similar to what I mentioned above.

  • 3
    Possible duplicate of How should I test randomness? – gnat Aug 29 '17 at 9:45
  • Remarks about question duplication belong in the comments, not in your question. – Robert Harvey Aug 29 '17 at 16:07
  • I don't think there's a need to create a new Random every time there. Have a Random as an instance member. – COME FROM Aug 30 '17 at 6:58
  • You should be looking at mathematical proofs, not tests. The tests are simply a backup as they prove nothing. – Frank Hileman Aug 30 '17 at 22:26
  • I'm not trying to test whether the randomness of the selected strings conform strictly to some statistical distribution. I'm trying to test 1. the selected strings are not the same every time 2. odd indexed strings are swapped with even indexed ones. – sayeed Aug 31 '17 at 9:13
10

The question you should be asking yourself is "What am I trying to prove?" (NOTE: "prove" is not used in the mathematical sense, but simply to test the validity of something). Unit tests are there to test that your application is functioning within it's designed parameters. Different tests for correctness require different approaches:

  • You can verify that all words in the returned the string exist in the global vocabulary
  • You can verify that there are, or are no repeats of words in that returned string
  • You can verify that there is no extraneous white space at the beginning and end
  • You can verify that the number of words enforces positive numbers
  • You can verify that the a list of 0 words provides an empty string

That might be sufficient for your needs. Those types of tests are also fairly robust. They won't break if the implementation of the random number generator changes. You are still working through the external interface to get at the internal features.

I think in this case it would prove both brittle and only marginally useful to test specific sequences of strings. There aren't really that many branch paths to worry about in your example. Think about the contracts and what it is you really need to ensure, and embrace the randomness.


The important thing to note is that you are not proving randomness. You are ensuring that your method behaves as expected.

If you were testing randomness, you would need to do some statistical analysis to prove the spread of random numbers was as expected, etc. Those kinds of tests would require "proof" in the mathematical sense of the word. Unit tests aren't the right tool for that job.

  • 1
    Unfortunately unit tests can rarely "prove" anything. This answer would be good if it encouraged actual proofs, informal or formal. The proofs are more important than the tests. – Frank Hileman Aug 30 '17 at 22:24
  • 2
    If this is your opinion, perhaps you aren't writing the right tests. Of course, this could also be a case of getting hung up on the semantics of the word "proof". In either case, the tests can easily be written to prove every one of those bullet points. Perhaps you are thinking in the mathematical concept of the word. If that's the case I'll concede that I'm no mathematical genius. I'm just a guy who learned by experience it's best to embrace the randomness and test what's conceptually important. – Berin Loritsch Aug 30 '17 at 23:42
  • 1
    Yes, I was using the word proof as commonly defined (mathematical proof). Informal proofs can work as well. But tests don't prove anything unless you can cover the whole state space, which is usually unrealistic. I think your answer would be fine if you did not use the word "proof" or "prove" in that way. – Frank Hileman Aug 30 '17 at 23:52
  • 1
    Just saying "you can test" instead of "you can prove", "testing" instead of "proving", would probably fix it. – Frank Hileman Aug 30 '17 at 23:54
  • 2
    Well, most things that you test are things that you want to prove, but proving is so much effort that you simply test them instead. How about using the word "ensure" instead of "prove"? – immibis Aug 31 '17 at 5:12
6

Use dependency injection. Create an IRandomNumberGenerator interface, and inject it into your class (as constructor argument) or function (as parameter) that needs it. It can be as simple as the following:

interface IRandomNumberGenerator
{
     int GetRandomNumber();
}

Now create two classes that implement that interface. Your real number generator class, and a mock. Your mock implementation will return a predefined number, your real implementation will return an actual random number. Test with the mock implementation.

class MockRandomNumberGenerator : IRandomNumberGenerator
{
     private int _fakeRandomNumber;

     public MockRandomNumberGenerator(int fakeRandomNumber)
     {
          _fakeRandomNumber = fakeRandomNumber
     }

     public int GetRandomNumber()
     {
          return _fakeRandomNumber;
     }
}

Under no condition test with actual random numbers being generated, your test results will no longer be reproducible in that case. I'm using C# syntax, as I'm not familiar with Java.

  • Unrelated to the question. – Frank Hileman Aug 30 '17 at 22:24
  • Seems like a lot of hassle when using the a real RNG with a specific seed, which works fine and disproves your last paragraph. – whatsisname Aug 31 '17 at 2:39
  • @whatsisname It works fine, until you want to ensure the test passes with a specific random number being generated. Mocking it takes 1 second (if you're not using Mocking libraries, you should), and gives you infinite flexibility. – Eternal21 Aug 31 '17 at 11:37
  • @FrankHileman Which part? – Eternal21 Aug 31 '17 at 11:38
  • 3
    @FrankHileman Because DI is the best way to decouple your unit under test from other responsibilities. As it is, the code was responsible both for generating random numbers, and selecting vocabulary words. That's why writing a test for it wasn't straightforward. Applying SRP (via DI) fixed that deficiency. – Eternal21 Aug 31 '17 at 16:29
3

When testing:

Use your random number generator with a particular seed that you specify. Then you will always get the same sequence. This makes it testable.

  • Do I add the seed to production code? I don't have access to RNG in test code. – sayeed Aug 29 '17 at 9:40
  • @sayeed That would force results to always be the same. I'm guessing that's not what you're after so no. Is there a configuration file? You could load a seed value from the configuration, and only if it is provided, set it in the program. Your production configuration would not have a seed and you can test your code in test environment with a predefined seed with no need to concern yourself of changing program functionality between test and production. – Neil Aug 29 '17 at 12:19
  • 5
    Honestly, I think this leads to brittle tests. Why not prove aspects of the methods that can be proven without worrying about specific results. – Berin Loritsch Aug 29 '17 at 13:53
1

Make random a member of the class under test. Inject a mocked/fixed-seed value in for testing, and a real one for production code. Use Dependency injection.

class someclassname {
    private Random _rng;

    constructor(Random rng)

    public String generate(int wordCount)

    private selectRandomStringsFromInternalVocabulary(int wordCount) //words would be a better name here btw
}

You may want to make the vocabulary a dependency as well.

Also, you might have a bug that this will fix anyway. I don't know about Java, but going new Random() in a method that is called very frequently (say in a fast loop) can go badly when the default constructor uses a time based seed.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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