Почему python mock patch не работает?

У меня есть два файла

spike.py

class T1(object):
    def foo(self, afd):
        return "foo"

    def get_foo(self):
        return self.foo(1)


def bar():
    return "bar"

test_spike.py:

from unittest import TestCase
import unittest
from mock import patch, MagicMock
from spike import T1, bar


class TestShit(TestCase):
    @patch('spike.T1.foo', MagicMock(return_value='patched'))
    def test_foo(self):
        foo = T1().get_foo()
        self.assertEqual('patched', foo)

    @patch('spike.bar')
    def test_bar(self, mock_obj):
        mock_obj.return_value = 'patched'
        bar = bar()
        self.assertEqual('patched', bar)


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

когда я запустил python test_spike.py, первый тестовый пример пройдет, но второй завершится ошибкой. и я переключаюсь на использование nosetests test_spike.py, тогда оба двоих не работают.

Я не понимаю, как это произошло? Эти случаи должны проходить все.

Ответ 1

Для test_foo вы неправильно используете патч. Вы должны использовать его следующим образом:

class TestShit(TestCase):
@patch.object(T1, 'foo', MagicMock(return_value='patched'))
def test_foo(self):
    foo = T1().get_foo()
    self.assertEqual('patched', foo)

который дает мне:

nosetests test_spike.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Теперь второй пример не работает, потому что вы импортируете функцию бара (получите ссылку на него), а затем попытайтесь высмеять его. Когда вы издеваетесь над чем-то, вы не можете изменить то, что удерживают ваши переменные (ссылка на оригинальную функцию). Чтобы исправить это, вы должны использовать предложенный метод @falsetru, например:

from unittest import TestCase
import unittest
from mock import patch
import spike


class TestShit(TestCase):
    @patch('spike.bar')
    def test_bar(self, mock_obj):
        mock_obj.return_value = 'patched'
        value = spike.bar()
        self.assertEqual('patched', value)


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

это дает мне:

python test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Но когда я пытаюсь запустить его с носом, я получаю:

 nosetests test_spike.py
F
======================================================================
FAIL: test_bar (src.test_spike.TestShit)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/home/zilva/envs/test/local/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "/home/zilva/git/test/src/test_spike.py", line 11, in test_bar
    self.assertEqual('patched', value)
AssertionError: 'patched' != 'bar'

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

Это происходит потому, что я исправляю не в нужном месте. Моя структура каталогов:

test/
└── src/
    ├── spike.py
    ├── test_spike.py
    └── __init__.py

и я запускаю тесты из каталога src, поэтому я должен исправлять с использованием пути из корневого каталога проекта, например:

@patch('src.spike.bar')

и это даст мне:

nosetests test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

или если я нахожусь в тестовом каталоге:

nosetests src/test_spike.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

Ответ 2

Доступ bar с помощью spike.bar. Импортировано bar не влияет на mock.patch.

from unittest import TestCase
import unittest
from mock import patch, MagicMock
from spike import T1
import spike # <----


class TestShit(TestCase):
    @patch('spike.T1.foo', MagicMock(return_value='patched'))
    def test_foo(self):
        foo = T1().get_foo()
        self.assertEqual('patched', foo)

    @patch('spike.bar')
    def test_bar(self, mock_obj):
        mock_obj.return_value = 'patched'
        bar = spike.bar() # <-----
        self.assertEqual('patched', bar)


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