Как использовать насмешку Python в модульном тесте

Большинство советов по насмешкам Python изложены в виде коротких фрагментов, выходящих за рамки модульного тестирования. Это работает, я пытаюсь следовать этому совету, но это не удается, как только я вставляю его в правильный модульный тест. Например, этот код, который выводит вывод в конце комментария:

# foo.py
def some_fn():
    return 'some_fn'

class Foo( object ):
    def method_1( self ):
        return some_fn()

# bar.py (depends on foo.py)
import foo
class Bar( object ):
    def method_2( self ):
        tmp = foo.Foo()
        return tmp.method_1()

# test.py (tests bar.py)
import unittest
import bar
from mock import patch

class Test( unittest.TestCase ):
    def setUp( self ):
        pass
    def tearDown( self ):
        pass

    @patch( 'foo.some_fn' )
    def test_bar( self, mock_some_fn ):
        mock_some_fn.return_value = 'test-val-1'
        tmp = bar.Bar()
        print tmp.method_2()
        self.assertEqual( tmp.method_2(), 'test-val-1' )  # line 32
        mock_some_fn.return_value = 'test-val-2'
        self.assertEqual( tmp.method_2(), 'test-val-2' )

if __name__ == "__main__":
    unittest.main()

Который я запускаю в PyDev и вижу:

Finding files... done.
Importing test modules ... done.

some_fn
======================================================================
FAIL: test_bar (test.foo.all.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/mock.py", line 1201, in patched
    return func(*args, **keywargs)
  File "/home/russ/dev/workspace/python-mocking/test/foo/all.py", line 32, in test_bar
    self.assertEqual( tmp.method_2(), 'test-val-1' )
AssertionError: 'some_fn' != 'test-val-1'

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

Удалите среду модульного тестирования, и этот код будет работать нормально (здесь только часть test.py всего файла):

...
# test.py (tests bar.py)
import bar
from mock import patch

@patch( 'foo.some_fn' )
def test_bar( mock_some_fn ):
  mock_some_fn.return_value = 'test-val-1'
  tmp = bar.Bar()
  print tmp.method_2()
  assert tmp.method_2() == 'test-val-1'
  mock_some_fn.return_value = 'test-val-2'
  assert tmp.method_2() == 'test-val-2'

который успешно производит при запуске:

~/dev/workspace/python-mocking/test/foo $ python
Python 2.7.5 (default, Nov  3 2014, 14:26:24) 
...
>>> import all0
>>> all0.test_bar()
test-val-1

Что еще я должен сделать, чтобы это работало в рамках модульного тестирования?


person Russ Bateman    schedule 11.02.2015    source источник
comment
Извините, я не могу воспроизвести вашу проблему. Попробуйте использовать self.assertEqual(tmp.method_2(), 'test-val-1') вместо assert ...., чтобы увидеть, в чем разница. Насколько я вижу, он должен работать отлично... Я довольно интенсивно использую mock в unittesting во многих версиях Python (2.7, 3.2 и 3.4) без каких-либо проблем, подобных этим.   -  person Michele d'Amico    schedule 12.02.2015
comment
Спасибо. Я исправляю свой код модульного теста так же, как вы просите, и я также вношу другие небольшие изменения, чтобы сделать вопрос более ясным. У меня есть несколько примеров решения этой проблемы с использованием различных фрагментов кода, собранных из Googleland. Пишу на Python всего 3 месяца. Я парень Java, привыкший широко использовать насмешки в модульных тестах и ​​поэтому пытаюсь понять это в Python.   -  person Russ Bateman    schedule 12.02.2015
comment
Можете ли вы также обновить вывод pyDev? Он по-прежнему ссылается на старый код, потому что в трассировке стека я прочитал assert tmp.method_2() == 'test-val-1'   -  person Michele d'Amico    schedule 12.02.2015
comment
Кстати, я сижу над множеством тестов, подобных тому, который вы показали, и у меня была такая проблема.   -  person Michele d'Amico    schedule 12.02.2015
comment
Обновил - спасибо. Я не понимаю вашего непосредственно предыдущего комментария: вы говорите, что не получаете того же самого?   -  person Russ Bateman    schedule 12.02.2015
comment
Да, я вырезал и вставил ваш код, и я не могу воспроизвести проблему   -  person Michele d'Amico    schedule 12.02.2015
comment
Мое лучшее предположение касается Eclipse/PyDev: можете ли вы попытаться вызвать версию unittest на консоли с помощью python test.py? Ставлю доллар, что это сработает.   -  person Michele d'Amico    schedule 12.02.2015
comment
Я удалил пыль со своего Eclipse и проверил ее и на Eclipse... Он работает как шарм. Вы должны были что-то забыть ... вы уверены, что patch работает с правильным модулем foo ... Есть ли какой-либо другой модуль foo в вашем пути к Python?   -  person Michele d'Amico    schedule 12.02.2015
comment
Спасибо, это не делается изолированно. Признаюсь, я не знаю, как это сделать из командной строки/консоли. Я запускаю, импортирую все и не знаю, что делать дальше (ничего из того, что я пытаюсь сделать, не работает), тогда как если я импортирую all0, а затем выполняю all0.test_bar(), это работает.   -  person Russ Bateman    schedule 12.02.2015
comment
Не из консоли Python, а из оболочки командной строки. (Я предполагаю, что вы используете Windows) Перейдите в тот же каталог, где находится test.py, и введите python.exe test.py... если не работает, замените python.exe полным путем python.exe. Кстати, я хотел бы отметить, что вы должны были что-то пропустить ... нет никакого способа воспроизвести это, вырезав и вставив ваш пример.   -  person Michele d'Amico    schedule 12.02.2015
comment
python all.py и test.py работают нормально, поэтому PyDev не работает. Большое спасибо, я думаю, мы закончили.   -  person Russ Bateman    schedule 13.02.2015


Ответы (1)


Ответ для меня, новичка в Python и PyDev, заключается в том, что это проблема PyDev. PyDev кажется очень обидчивым в отношении того, как настроено модульное тестирование проекта. Мне удалось заставить этот код работать, связав воедино новый проект, настроенный точно так же, включая отдельные файлы.

python-mocking
+-- mocking_example
    +-- test
    |   +-- __init__.py
    |   `-- test.py
    +-- __init__.py
    +-- bar.py
    `-- foo.py

Возможно, стоит отметить, что приведенная выше структура делает так, что в test.py bar должен быть импортирован следующим образом:

import mocking_example.bar

и потребляется таким образом, т.е.:

tmp = mocking_example.bar.Bar()
person Russ Bateman    schedule 19.02.2015
comment
Я думаю, что ваша проблема заключается в конфигурации пути python для вашего проекта: если вы добавите mocking_example к своему пути python, вы сможете получить к нему доступ без каких-либо проблем. - person Michele d'Amico; 25.02.2015
comment
Да, спасибо, я вижу. Я не хотел влиять на переносимость моего проекта PyDev и сопротивлялся этому. Это отвечает на тесно связанный вопрос: stackoverflow.com/questions/9249995/ - person Russ Bateman; 25.02.2015
comment
Я вижу, что вы являетесь разработчиком Java, и поэтому Eclipse — ваш дом, но я хотел бы порекомендовать вам попробовать PyCharm... это очень хороший инструмент! - person Michele d'Amico; 25.02.2015