#python #unit-testing #mocking
#python #модульное тестирование #издевательство
Вопрос:
Я хочу choice
возвращать одно и то же значение 1000
каждый раз в моем unittest. Следующий код не работает.
import unittest
from random import choice
from mock import mock
def a():
return choice([1, 2, 3])
class mockobj(object):
@classmethod
def choice(cls, li):
return 1000
class testMock(unittest.TestCase):
def test1(self):
with mock.patch('random.choice', mockobj.choice):
self.assertEqual(a(), 1000)
Сообщение об ошибке выглядит следующим образом:
Failure
Traceback (most recent call last):
File "test.py", line 15, in test1
self.assertEqual(a(), 1000)
AssertionError: 3 != 1000
Как я должен изменить его, чтобы заставить его работать? Я использую python2.7
Комментарии:
1. Что это за платформа тестирования python? Я не знаю о «mockobj». Почему этот код не работает — что он делает?
2. @DannyStaple Извините за неудобства.
mockobj
это просто класс, который я создал, я добавил его в код, чтобы вы могли просто запустить его, чтобы воспроизвести проблему.
Ответ №1:
Проблема здесь в том, что a()
используется не исправленная версия random.choice
.
Сравните функции a
и b
:
import random
from random import choice
def a():
return choice([1, 2, 3])
def b():
return random.choice([1, 2, 3])
def choice1000(values):
return 1000
import unittest.mock as mock
with mock.patch('random.choice', choice1000):
print('a', a())
print('b', b())
Он печатает, например:
a 3
b 1000
Почему?
Проблема в этой строке:
from random import choice
Он импортируется random
, а затем сохраняется random.choice
в новой переменной с именем choice
.
Позже mock.patch
исправил оригинал random.choice
, но не локальный choice
.
Могу ли я исправить локальный? ДА:
with mock.patch('__main__.choice', choice1000):
print('a', a())
print('b', b())
Теперь он печатает, например
a 1000
b 1
(Я использовал '__main__'
, потому что я поместил этот код в файл верхнего уровня — в вашем случае это может быть что-то другое)
Итак, что делать?
Либо исправьте все, либо используйте другой подход. Например, patch a()
вместо choice()
.
Альтернативное решение
В этом случае, когда вы хотите протестировать поведение random
функций, может быть лучше использовать начальное значение
def a():
return random.choice([1, 2, 3, 1000])
def test1(self):
random.seed(0)
self.assertEqual(a(), 1000)
Вы не можете заранее знать, какие случайные значения будут сгенерированы для определенного начального числа, но вы можете быть уверены, что они всегда будут одинаковыми. Это именно то, что вам нужно в тестах.
В последнем примере выше я тестировал a()
after random.seed(0)
once, и он вернул 1000, поэтому я могу быть уверен, что он будет делать это каждый раз:
>>> import random
>>> random.seed(0)
>>> print (random.choice([1, 2, 3, 1000]))
1000
>>> random.seed(0)
>>> print (random.choice([1, 2, 3, 1000]))
1000
>>> random.seed(0)
>>> print (random.choice([1, 2, 3, 1000]))
1000
>>> random.seed(0)
>>> print (random.choice([1, 2, 3, 1000]))
1000
Ответ №2:
Я не знаю, что такое mockobj из тестов, но то, что вы можете сделать, это.
@mock.patch('random.choice')
def test1(self, choice_mock):
choice_mock.return_value = 1000
self.assertEqual(a(), 1000)
Ответ №3:
Я хотел бы улучшить ответ @Alex с помощью полного сценария, чтобы лучше понимать и быть адаптируемым к другим случаям.
import random
from unittest import TestCase, mock
letters = ['A', 'B', 'C', 'D']
def get_random_words(): # Simple function using choice
l = []
for _ in range(3):
l.append(random.choice(letters))
return "".join(l)
class TestRandom(TestCase):
@mock.patch('random.choice') # *(1)
def test_get_random_words(self, mock_choice):
mock_choice.side_effect = ['A','b','D','Z'] # *(2)
result = get_random_words()
self.assertEqual(result, 'AbD', 'Does not generate correct string')
Соображения
* (1) Для этого примера функция находится внутри того же файла, но в случае, если она находится в другом файле, вы должны изменить путь к исправлению, например: @mock.patch('your_package.your_file.your_function.random.choice')
* (2) В этом случае get_random_words
функция вызывается random.choice
3 раза. Вот почему вы должны поместить в него равные или более элементов mock_choice.side_effect
. Это потому, что если в нем меньше элементов, он выдаст StopIteration
ошибку.
Комментарии:
1. 1 за добавление опции для заглушки функции с разными значениями в цикле. Это достижимо с помощью pytest-mock?