[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