4

I'm still pretty new to programming and just learning how to unittest. I need to test a function that returns a random value. I've so far found answers suggesting the use of a specific seed value so that the 'random' sequence is constant and can be compared. This is what I've got so far:

This is the function I want to test:

import random

def roll():
    '''Returns a random number in the range 1 to 6, inclusive.'''
    return random.randint(1, 6)

And this is my unittest:

class Tests(unittest.TestCase):

    def test_random_roll(self):
        random.seed(900)
        seq = random.randint(1, 6)
        self.assertEqual(roll(), seq)

How do I set the corresponding seed value for the PRNG in the function so that it can be tested without writing it into the function itself? Or is this completely the wrong way to go about testing a random number generator?

Thanks

TobyBBrown
  • 157
  • 4
  • 10
  • Might want a take a look at [this question](https://stackoverflow.com/questions/186619/how-to-unit-test-a-pseudo-random-number-generator) – Jared Goguen Nov 07 '17 at 18:01
  • @JaredGoguen Thanks, I'd seen it but couldn't quite fully understand what they were suggesting. Seems like having the 'seq = random.randint' was my problem and I wasn't understanding that the function call would use the seed specified in the test. – TobyBBrown Nov 07 '17 at 18:11

2 Answers2

4

The other answers are correct as far as they go. Here I'm answering the deeper question of how to test a random number generator:

Your provided function is not really a random number generator, as its entire implementation depends on a provided random number generator. In other words, you are trusting that Python provides you with a sensible random generator. For most purposes, this is a good thing to do. If you are writing cryptographic primitives, you might want to do something else, and at that point you would want some really robust test strategies (but they will never be enough).

Testing a function returns a specific sequence of numbers tells you virtually nothing about the correctness of your function in terms of "producing random numbers". A predefined sequence of numbers is the opposite of a random sequence.

So, what do you actually want to test? For 'roll' function, I think you'd like to test:

  1. That given 'enough' rolls it produces all the numbers between 1 and 6, preferably in 'approximately' equal proportions.
  2. That it doesn't produce anything else.

The problem with 1. is that your function is defined to be a random sequence, so there is always a non-zero chance that any hard limits you put in to define 'enough' or 'approximately equal' will occasionally fail. You could do some calculations to pick some limits that would make sure your test is unlikely to fail more than e.g. 1 in a billion times, or you could slap a random.seed() call that will mean it will never fail if it passes once (unless the underlying implementation from Python changes).

Item 2. could be 'tested' more easily - generate some large 'N' number of items, check that all are within expected outcome.

For all of this, however, I'd ask what value the unit tests actually are. You literally cannot write a test to check whether something is 'random' or not. To see whether the function has a reasonable source of randomness and uses it correctly, tests are useless - you have to inspect the code. Once you have done that, it's clear that your function is correct (providing Python provides a decent random number generator).

In short, this is one of those cases where unit tests provide extremely little value. I would probably just write one test (item 2 above), and leave it at that.

spookylukey
  • 6,380
  • 1
  • 31
  • 34
3

By seeding the prng with a known seed, you know which sequence it will produce, so you can test for this sequence:

class Tests(unittest.TestCase):
    def test_random_roll(self):
        random.seed(900)
        self.assertEqual(roll(), 6)
        self.assertEqual(roll(), 2)
        self.assertEqual(roll(), 5)
thebjorn
  • 26,297
  • 11
  • 96
  • 138
  • Thanks! Seems like I wasn't quite getting that the function call would use the seed from the test automatically and that I could just manually check values from that seed. – TobyBBrown Nov 07 '17 at 18:13