Mockando o que tá meio mockado
Um mock especial para Python unittest
Ah, um problema
E estava eu preocupado com um teste que deveria escrever para uma aplicação Django. Era muita coisa pra preparar o contexto da função a ser chamada. E deixei quieto, no fim de semana resolvo isso, só isso.
Com a cabeça mais fria, liguei-me que eu estava querendo retestar o que estava testado, e, portando, a pior parte de tudo poderia ser resolvida com um mock.
Coisa simples, pois a aplicação está bem estruturada, funções muito específicas, que são chamadas por outras funções específicas, enfim.
O patch
tava lá, com o nome certinho da função e o caminho.
Simulando
Como se trata de código da empresa em que trabalho, aqui vai um simulado, que está:
.
├── __init__.py
├── mod1
│ ├── __init__.py
│ ├── services.py
│ └── tests
│ ├── __init__.py
│ └── test_mod1.py
└── mod2
├── __init__.py
├── services.py
└── tests
├── __init__.py
└── test_mod2.py
Isso é o mod2/services.py
:
def mod2_function():
return 'This is REAL mod2!'
E isso é o mod1/services.py
:
from mod2.services import mod2_function
def mod1_function():
mod2 = mod2_function()
return 'From _mod1_ I get this from _mod2_: [{}]'.format(mod2)
O detalhe é que mod1
usa mod2
.
O teste, intencionalmente falho, para mod1/tests/test_mod1.py
:
from unittest import TestCase
from ..services import mod1_function
class TestMod1(TestCase):
def test_mod1(self):
expected = 'I don\'t remember'
result = mod1_function()
self.assertEqual(result, expected)
E o teste para mod2/testes/test_mod2.py
:
from unittest import TestCase
from ..services import mod2_function
class TestMod2(TestCase):
def test_mod2(self):
expected = 'This is REAL mod2!'
mod2_result = mod2_function()
self.assertEqual(mod2_result, expected)
Rodando os testes:
paulohrpinheiro@phrp:~/D/p/c/mocking|master✓
➤ python -munittest -v
test_mod1 (mod1.tests.test_mod1.TestMod1) ... FAIL
test_mod2 (mod2.tests.test_mod2.TestMod2) ... ok
======================================================================
FAIL: test_mod1 (mod1.tests.test_mod1.TestMod1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/paulohrpinheiro/Dropbox/projetos/coisas/mocking/mod1/tests/test_mod1.py", line 11, in test_mod1
self.assertEqual(result, expected)
AssertionError: 'From _mod1_ I get this from _mod2_: [This is REAL mod2!]' != "I don't remember"
- From _mod1_ I get this from _mod2_: [This is REAL mod2!]
+ I don't remember
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=1)
Deu erro, como esperado. Eu fiz isso para ressaltar o fato de mod1
usar mod2
. Essa era a minha situação real. Agora, para arrumar o teste vou mockar um valor:
➤ git diff
diff --git a/mocking/mod1/tests/test_mod1.py b/mocking/mod1/tests/test_mod1.py
index 7ff23c8..a772241 100644
--- a/mocking/mod1/tests/test_mod1.py
+++ b/mocking/mod1/tests/test_mod1.py
@@ -1,11 +1,14 @@
from unittest import TestCase
+from unittest.mock import patch
from ..services import mod1_function
class TestMod1(TestCase):
- def test_mod1(self):
- expected = 'I don\'t remember'
+ @patch('mod2.services.mod2_function')
+ def test_mod1(self, mock_mod2):
+ mock_mod2.return_value = 'MOCKED!'
+ expected = 'From _mod1_ I get this from _mod2_: [MOCKED!]'
result = mod1_function()
self.assertEqual(result, expected)
Rodando o teste alterado:
➤ python -munittest -v
test_mod1 (mod1.tests.test_mod1.TestMod1) ... FAIL
test_mod2 (mod2.tests.test_mod2.TestMod2) ... ok
======================================================================
FAIL: test_mod1 (mod1.tests.test_mod1.TestMod1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/paulohrpinheiro/.pyenv/versions/3.6.4/lib/python3.6/unittest/mock.py", line 1179, in patched
return func(*args, **keywargs)
File "/home/paulohrpinheiro/Dropbox/projetos/coisas/mocking/mod1/tests/test_mod1.py", line 13, in test_mod1
self.assertEqual(result, expected)
AssertionError: 'From _mod1_ I get this from _mod2_: [This is REAL mod2!]' != 'From _mod1_ I get this from _mod2_: [MOCKED!]'
- From _mod1_ I get this from _mod2_: [This is REAL mod2!]
? ^^^^^^^^^ ^^^^^^^
+ From _mod1_ I get this from _mod2_: [MOCKED!]
? ^^^^ ^
Lendo e relendo alguns textos, encontrei esse, muito claro a respeito de meu problema:
Patching tip using mocks in python unit tests, do Steve's Blog
Que me deu a luz para essa alteração:
➤ git diff
diff --git a/mocking/mod1/tests/test_mod1.py b/mocking/mod1/tests/test_mod1.py
index 8f66098..2b05c63 100644
--- a/mocking/mod1/tests/test_mod1.py
+++ b/mocking/mod1/tests/test_mod1.py
@@ -5,7 +5,7 @@ from ..services import mod1_function
class TestMod1(TestCase):
- @patch('mod2.services.mod2_function', return_value='MOCKED!')
+ @patch('mod1.services.mod2_function', return_value='MOCKED!')
def test_mod1(self, mock_mod2):
expected = 'From _mod1_ I get this from _mod2_: [MOCKED!]'
result = mod1_function()
Finalmente:
➤ python -munittest -v
test_mod1 (mod1.tests.test_mod1.TestMod1) ... ok
test_mod2 (mod2.tests.test_mod2.TestMod2) ... ok
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
A razão disso
Quando, em mod1/services.py
realizo o import
, estou adicionando ao namespace local a função mod2_function
. Por isso que o caminho para o patch deve ser mod1.services.mod2_funcion
, e não o caminho de onde a função foi escrita.