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

Ben Finney ben+python at benfinney.id.au
Sat Mar 19 01:05:58 EDT 2016


boB Stepp <robertvstepp at gmail.com> writes:

> 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?

You need to make the system deterministic during the test run. Since the
random number generator (RNG) is a dependency that your test cases don't
entirely control, that is a dependency that makes the system
non-deterministic.

One way to do that is to allow the dependency – the RNG – to be
specified as part of the API of the system under test::

    def roll_die(num_sides, rng=None):

This is known as the “dependency injection” pattern. The system makes a
deliberate API for its dependencies, and allows the caller to specify
the object that represents the dependency.

    <URL:https://en.wikipedia.org/wiki/Dependency_injection>

The system does not directly use the external dependency; it uses the
dependency supplied, as an abstraction::

    import random

    def roll_die(num_sides, rng=None):
        """ Return a result from a random die of `num_sides` sides.

            :param num_sides: The number of sides on the die.
            :param rng: An instance of `random.Random`, the random
                number generator to use.
                Default: the `random.random` instance.
            :return: The integer result from the die roll.

            The die is defined to have equal-probability faces, numbered
            from 1 to `num_sides`.

            """
        if rng is None:
            rng = random.random

        result = rng.randint(1, num_sides)
        return result

In this case the dependency has a sensible default, so the caller
doesn't *need* to specify the dependency. That's not always true, and
dependency injection often entails re-designing the API and callers must
adapt to that new API.


With that re-design, the test module can create an RNG that behaves as
expected. Your test cases can create their own RNG instance and specify
its seed, which means the same numbers will be generated every time from
that seed.

    import unittest
    import random

    from . import die_roller    # The system under test.

    class roll_die_TestCase(unittest.TestCase):
        """ Test cases for the `roll_die` function. """

        def setUp(self):
            """ Set up test fixtures. """
            # Set the seed value such that the system can't anticipate
            # it, but such that we can re-use it any time.            
            self.seed = id(self)

            self.rng = random.Random(self.seed)

        def test_result_in_expected_range(self):
            """ The result should be in the range expected for the die. """
            test_num_sides = 6
            expected_range = range(1, test_num_sides + 1)
            result = die_roller.roll_die(test_num_sides, rng=self.rng)
            self.assertIn(result, expected_range)

        def test_result_is_from_rng(self):
            """ The result should be produced by the supplied RNG. """
            test_num_sides = 6
            self.rng.seed(self.seed)
            expected_result = self.rng.randint(1, test_num_sides)
            self.rng.seed(self.seed)
            result = die_roller.roll_die(test_num_sides, rng=self.rng)
            self.assertEqual(result, expected_result)

> And I do not see how I can test for an appropriate "randomness" to the
> numbers the function generates without to a lot of iterations,

That's the thing about randomness. By definition, you can't determine it
:-) But by providing your own RNG that is under control of the test
cases, you can know what numbers it will produce by re-setting it to a
known seed.

Hopefully you can take this as an example of how to better design
systems so they don't have tightly-entangled external dependencies.

-- 
 \      “The good thing about science is that it's true whether or not |
  `\              you believe in it.” —Neil deGrasse Tyson, 2011-02-04 |
_o__)                                                                  |
Ben Finney



More information about the Tutor mailing list