Yetti
Yetti

Reputation: 327

Python injecting random number into tests

I have written code like this :

def choice(states):
    states = list(states)
    rnd = random.random()
    for state, p in states:
        rnd -= p
        if rnd <= 0:
            return state

And I need to create some tests :

import unittest
class Tests(unittest.TestCase):
    def test_choice(self):
        assertEquals(choice(states),something_equl)

How am I supposed to inject my own random number into test? to get deterministic results?

Upvotes: 1

Views: 3673

Answers (3)

Alex Montoya
Alex Montoya

Reputation: 5099

I would like to improve the response with a full script to better understand and be adaptable to other cases.

import random
from unittest import TestCase, mock


def get_random_words(): # Simple function using choice
  l = []
  for _ in range(3):
      l.append(random.random(0, 10))
    
  return "".join([str(n) for n in l])

class TestRandom(TestCase):

   @mock.patch('random.random') # *(1)
   def test_get_random_words(self, mock_random):
    
      mock_random.side_effect = [1,7,3,6] # *(2)
      result = get_random_words()
    
      self.assertEqual(result, '173', 'Does not generate correct numbers')

Considerations

*(1) For this example, the function is inside the same file, but in case it is in another file you must change the path of the patch Ex: @mock.patch('your_package.your_file.your_function.random.random')

*(2) For this case, the get_random_words function calls random.random 3 times. This is why you must put equal or more items inside mock_random.side_effect. This is because if it has fewer items it will throw the StopIteration error.

Upvotes: 0

alecxe
alecxe

Reputation: 473873

Mock the random.random() function, example:

import random
import unittest
import mock


def choice(states):
    states = list(states)
    rnd = random.random()
    for state, p in states:
        rnd -= p
        if rnd <= 0:
            return state


class Tests(unittest.TestCase):
    @mock.patch('random.random')
    def test_first_state_fires(self, random_call):
        random_call.return_value = 1
        self.assertEquals(choice([(1, 1)]), 1)

    @mock.patch('random.random')
    def test_returns_none(self, random_call):
        random_call.return_value = 2
        self.assertIsNone(choice([(1, 1)]))

Upvotes: 6

Martijn Pieters
Martijn Pieters

Reputation: 1122022

You can use the unittest.mock library to patch out the random() function. The library is part of Python 3.3 and up, you can install it separately as mock for older versions:

try:
    from unittest import mock
except ImportError:
    import mock

class Tests(unittest.TestCase):
    @mock.patch('random.random')
    def test_choice(self, mock_random):
        mock_random.return_value = 0.42
        assertEquals(choice(states),something_equl)

Upvotes: 4

Related Questions