You are right, your tests should not verify that the random
module is doing its job; a unittest should only test the class itself, not how it interacts with other code (which should be tested separately).
It is of course entirely possible that your code uses random.randint()
wrong; or you be calling random.randrange(1, self._sides)
instead and your die never throws the highest value, but that'd be a different kind of bug, not one you could catch with a unittest. In that case, your die
unit is working as designed, but the design itself was flawed.
In this case, I'd use mocking to replace the randint()
function, and only verify that it has been called correctly. Python 3.3 and up comes with the unittest.mock
module to handle this type of testing, but you can install the external mock
package on older versions to get the exact same functionality
import unittest
try:
from unittest.mock import patch
except ImportError:
# < python 3.3
from mock import patch
@patch('random.randint', return_value=3)
class TestDice(unittest.TestCase):
def _make_one(self, *args, **kw):
from die import Die
return Die(*args, **kw)
def test_standard_size(self, mocked_randint):
die = self._make_one()
result = die.roll()
mocked_randint.assert_called_with(1, 6)
self.assertEqual(result, 3)
def test_custom_size(self, mocked_randint):
die = self._make_one(sides=42)
result = die.roll()
mocked_randint.assert_called_with(1, 42)
self.assertEqual(result, 3)
if __name__ == '__main__':
unittest.main()
With mocking, your test is now very simple; there are only 2 cases, really. The default case for a 6-sided die, and the custom sides case.
There are other ways to temporarily replace the randint()
function in the global namespace of Die
, but the mock
module makes this easiest. The @mock.patch
decorator here applies to all test methods in the test case; each test method is passed an extra argument, the mocked random.randint()
function, so we can test against the mock to see if it it indeed has been called correctly. The return_value
argument specifies what is returned from the mock when it is called, so we can verify that the die.roll()
method indeed returned the 'random' result to us.
I've used another Python unittesting best practice here: import the class under test as part of the test. The _make_one
method does the importing and instantiation work within a test, so that the test module will still load even if you made a syntax error or other mistake that'll prevent the original module to import.
This way, if you made a mistake in the module code itself, the tests will still be run; they'll just fail, telling you about the error in your code.
To be clear, the above tests are simplistic in the extreme. The goal here is not to test that random.randint()
has been called with the right arguments, for example. Instead, the goal is to test that the unit is producing the right results given certain inputs, where those inputs include the results of other units not under test. By mocking the random.randint()
method you get to take control over just another input to your code.
In real world tests, the actual code in your unit-under-test is going to be more complex; the relationship with inputs passed to the API and how other units are then invoked can be interesting still, and mocking will give you access to intermediate results, as well as let you set the return values for those calls.
For example, in code that authenticates users against a 3rd party OAuth2 service (a multi-stage interaction), you want to test that your code is passing the right data to that 3rd party service, and lets you mock out different error responses that that 3rd party service would return, letting you simulate different scenarios without having to build a full OAuth2 server yourself. Here it is important to test that information from a first response have been handled correctly and have been passed on to a second stage call, so you do want to see that the mocked service is being called correctly.