[Tutor] How to test function using random.randint()?

Steven D'Aprano steve at pearwood.info
Sat Mar 19 08:29:10 EDT 2016


On Fri, Mar 18, 2016 at 11:26:13PM -0500, boB Stepp wrote:
> If I had a function to roll a die, such as:
> 
> import random
> 
> def roll_die(num_sides):
>     return random.randint(1, num_sides)
> 
> How would I write unit tests for this? 

(1) One way would be to monkey-patch the random module with your own. 
Something like this:



import mymodule  # The module containing the roll_die function.

class random:
    @staticmethod
    def myrandomint(a, b):
        return 5

save = mymodule.random
try:
    mymodule.random = random
    assert mymodule.roll_die(100) == 5
finally:
    mymodule.random = save



Better would be to set up myrandomint so that it returns a specific 
sequence of values, so you can write:

    assert mymodule.roll_die(100) == 5
    assert mymodule.roll_die(100) == 23
    assert mymodule.roll_die(100) == 17
    assert mymodule.roll_die(100) == 2

(for example). One way to get a known sequence of values is like this:

values = [5, 23, 17, 2]
it = iter(values)
func = functools.partial(next, it, 999)

Now func is a function that takes no arguments and returns 5 the first 
time you call it, 23 the second time, and so on, and then returns 999 
forever afterwards.



(2) Another way is to use the random seed to get a predictable series of 
values:

import mymodule
mymodule.random.seed(95)
assert mymodule.roll_die(6) == 3
assert mymodule.roll_die(6) == 2
assert mymodule.roll_die(6) == 6
assert mymodule.roll_die(6) == 6
assert mymodule.roll_die(6) == 5


But beware: Python only guarantees that changing the seed will give the 
same sequence of values for random.random(), *not* for the other random 
functions. So they can change according to the version of Python you are 
using. However, you know that given some specific version, the output of 
randint(a, b) will be the same when given the same seed. It may be 
different for another version.


(3) A third approach is "dependency injection". Re-write your roll_die 
function like this:


def roll_die(num_sides, randint=None):
    if randint is None:
        # Use the default randint
        from random import randint
    return randint(1, num_sides)


Now, when testing, you can *explicitly* provide a "randint" function 
that returns whatever you like. Use the monkey-patch technique above, 
or your own custom random generator:


import random
myrand = random.Random()  # Make an independent PRNG.
myrand.seed(1000)  # Seed to a known value
# Record the values it gives.
values = [myrand.randint(1, 6) for i in range(1000)]
myrand.seed(1000)  # Restore the seed.
for expected_value in values:
    assert mymodule.roll_die(6, myrand.randint) == expected_value



-- 
Steve


More information about the Tutor mailing list