Использование mocks, mockery и mocking — отличный способ упростить и улучшить модульное тестирование в Python. Тем не менее, высмеивание разных версий Python может показаться, что Python дразнит или смеется над вами в пренебрежительной или презрительной манере. Вот краткое руководство, чтобы избежать некоторых ключевых болевых точек.

Под сводкой я показываю примеры тестирования, которые делают то же самое, но различаются в зависимости от того, какие версии Python вы хотите поддерживать.

Резюме

Питон 3.6+:

  • Импорт из unittest.mock
  • Можно использовать assert_called_with()
  • Можно использовать assert_called_once() и assert_not_called()

Питон 3.5:

  • Импорт из unittest.mock
  • Можно использовать assert_called_with()
  • Невозможно использовать assert_called_once() или assert_not_called()

Питон 3.4:

  • Импорт из unittest.mock
  • Можно использовать assert_called_with()
  • Можно использовать assert_called_once(), но нет assert_not_called() 🤔

Питон 2:

  • Импорт из mock
  • Можно использовать assert_called_with()
  • Невозможно использовать assert_called_once() или assert_not_called()

Пример: насмешка в Python 3.6+

Если вам нужно только поддерживать Python 3.6+ при тестировании, вы можете импортировать из unittest.mock и использовать удобные методы assert_called_once() и assert_not_called().

В этом фрагменте примера показано тестирование собственной утилиты uhlist.utils.copytree(), которая добавляет функциональность к версии, найденной в shutil. Используя mock, мы можем избежать фактического копирования каких-либо файлов в рамках теста.

import pytest
import uhlist.utils
from unittest.mock import patch
@patch('shutil.rmtree', autospec=True)
@patch('shutil.copytree', autospec=True)
def test_copytree_no_force(mock_copy, mock_rm):
    """Test that copytree results in correct calls."""
    random_src = 'aba51e65-afd2-5020-8117-195f75e64258'
    random_dst = 'f74d03de-7c1d-596f-83f3-73748f2e238f'
    uhlist.utils.copytree(random_src, random_dst)
    mock_copy.assert_called_with(random_src, random_dst)
    mock_copy.assert_called_once()
    mock_rm.assert_not_called()

Насмешки в Python 3.5+

Этот фрагмент будет работать на Python 3.5+. Этот код импортирует из unittest.mock, но не может использовать вспомогательные методы assert_called_once() и assert_not_called().

import pytest
import uhlist.utils
from unittest.mock import patch
@patch('shutil.rmtree', autospec=True)
@patch('shutil.copytree', autospec=True)
def test_copytree(mock_copy, mock_rm):
    """Test that copytree results in correct calls."""
    random_src = 'aba51e65-afd2-5020-8117-195f75e64258'
    random_dst = 'f74d03de-7c1d-596f-83f3-73748f2e238f'
    uhlist.utils.copytree(random_src, random_dst)
    mock_copy.assert_called_with(random_src, random_dst)
    assert mock_copy.call_count == 1
    assert mock_rm.call_count == 0

Насмешки в Python 3.4+

Этот фрагмент будет работать на Python 3.4+. Этот код импортирует из unittest.mock и использует удобный метод assert_called_once(), но не может использовать assert_not_called(). 🤔

import pytest
import uhlist.utils
from unittest.mock import patch
@patch('shutil.rmtree', autospec=True)
@patch('shutil.copytree', autospec=True)
def test_copytree(mock_copy, mock_rm):
    """Test that copytree results in correct calls."""
    random_src = 'aba51e65-afd2-5020-8117-195f75e64258'
    random_dst = 'f74d03de-7c1d-596f-83f3-73748f2e238f'
    uhlist.utils.copytree(random_src, random_dst)
    mock_copy.assert_called_with(random_src, random_dst)
    mock_copy.assert_called_once()
    assert mock_rm.call_count == 0

Насмешки в Python 2.6+

Если вы хотите, чтобы один и тот же тест работал во всех доступных версиях Python, этот фрагмент кода дает вам представление о том, как это делается.

Примечательно, что в 2.6 и 2.7 импорт происходит из mock вместо unittest.mock. Для одновременной поддержки более поздних версий Python нам понадобится файл try/except. Как и в 3.5, нет ни вспомогательного метода, ни assert_called_once(), ни assert_not_called():

import pytest
import uhlist.utils
try:
    from unittest.mock import patch
except ImportError:
    from mock import patch
@patch('shutil.rmtree', autospec=True)
@patch('shutil.copytree', autospec=True)
def test_copytree(mock_copy, mock_rm):
    """Test that copytree results in correct calls."""
    random_src = 'aba51e65-afd2-5020-8117-195f75e64258'
    random_dst = 'f74d03de-7c1d-596f-83f3-73748f2e238f'
    uhlist.utils.copytree(random_src, random_dst)
    mock_copy.assert_called_with(random_src, random_dst)
    assert mock_copy.call_count == 1
    assert mock_rm.call_count == 0