Ви маєте рацію, ваші тести не повинні підтверджувати, що random
модуль виконує свою роботу; unittest повинен перевірити лише сам клас, а не те, як він взаємодіє з іншим кодом (який слід перевірити окремо).
Звичайно, цілком можливо, що ваш код використовує random.randint()
неправильно; або ви random.randrange(1, self._sides)
натомість дзвоните, і ваша смерть ніколи не викидає найвищу цінність, але це був би інший вид помилок, а не той, якого ви могли б спіймати з єдиним тестом. У такому випадку ваш die
пристрій працює як розроблено, але сама конструкція була помилковою.
У цьому випадку, я хотів би використовувати глузливий , щоб замінити на randint()
функцію, і тільки перевірити , що це було називається правильно. Python 3.3 і новіша версія поставляється з unittest.mock
модулем для обробки цього виду тестування, але ви можете встановити зовнішній mock
пакет на старих версіях, щоб отримати точно такий же функціонал
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()
Знущаючись, ваш тест зараз дуже простий; дійсно є лише 2 випадки. Випадок за замовчуванням для 6-стороннього штампу, а також користувацькі сторони.
Є й інші способи тимчасової заміни randint()
функції в глобальному просторі імен Die
, але mock
модуль робить це найпростішим. Тут @mock.patch
декоратор застосовується до всіх методів випробувань у тестовому випадку; Кожен метод тестування передається додатковим аргументом, random.randint()
функцією макету, тому ми можемо протестувати проти макету, щоб побачити, чи він дійсно був названий правильно. В return_value
аргумент вказує , що повертаються з знущатися , коли його називають, так що ми можемо перевірити , що die.roll()
метод дійсно повернув «випадковий» результат для нас.
Тут я використав ще одну найкращу практику тестування модулів Python: імпортуйте досліджуваний клас як частину тесту. _make_one
Метод робить імпорт і інстанцірованія роботу в рамках тесту , так що тест модуль все одно буде завантажувати навіть якщо ви зробили помилку синтаксису або іншу помилку , яка буде перешкоджати оригінальний модуль для імпорту.
Таким чином, якщо ви помилилися в самому коді модуля, тести все одно будуть запущені; вони просто вийдуть з ладу, повідомляючи про помилку у вашому коді.
Щоб було зрозуміло, наведені вище тести є крайніми спрощеними. Ціль тут - не перевірити, наприклад, що random.randint()
було викликано правильними аргументами. Натомість мета - перевірити, чи пристрій виробляє правильні результати за певними входами, де ці дані включають результати інших одиниць, які не перевіряються. Знущаючись над random.randint()
методом, ви можете взяти під контроль лише інший вхід до вашого коду.
У реальних тестах фактичний код вашої одиниці тестування буде складнішим; взаємозв’язок із вхідними даними, переданими в API, і те, як потім викликаються інші одиниці, може бути цікавим, але глузування дасть вам доступ до проміжних результатів, а також дозволить встановити значення повернення для цих викликів.
Наприклад, у коді, який автентифікує користувачів на сторонній службі OAuth2 (багатоступенева взаємодія), ви хочете перевірити, що ваш код передає правильні дані цій сторонній службі, і дозволяє вам висміювати різні відповіді на помилки, що Служба третьої сторони повернеться, дозволяючи вам імітувати різні сценарії без необхідності самостійно створювати повноцінний сервер OAuth2. Тут важливо перевірити, чи інформація з першої відповіді була оброблена правильно та була передана на виклик другої стадії, тому ви хочете побачити, що знущається служба викликається правильно.