update python time mocking python-mock

python - update - Patch__call__ de una función



mongoengine upsert (3)

Necesito parchar la fecha y hora actual en las pruebas. Estoy usando esta solución:

def _utcnow(): return datetime.datetime.utcnow() def utcnow(): """A proxy which can be patched in tests. """ # another level of indirection, because some modules import utcnow return _utcnow()

Luego, en mis pruebas, hago algo como:

with mock.patch(''***.utils._utcnow'', return_value=***): ...

Pero hoy se me ocurrió una idea, que podría simplificar la implementación __call__ de la función utcnow lugar de tener un _utcnow adicional.

Esto no funciona para mi:

from ***.utils import utcnow with mock.patch.object(utcnow, ''__call__'', return_value=***): ...

¿Cómo hacer esto elegantemente?


Como se comentó sobre la pregunta, dado que datetime.datetime está escrito en C, Mock no puede reemplazar los atributos de la clase (ver Mocking datetime.today por Ned Batchelder). En cambio, puedes usar Freezegun .

$ pip install freezegun

Aquí hay un ejemplo:

import datetime from freezegun import freeze_time def my_now(): return datetime.datetime.utcnow() @freeze_time(''2000-01-01 12:00:01'') def test_freezegun(): assert my_now() == datetime.datetime(2000, 1, 1, 12, 00, 1)

Como mencionas, una alternativa es rastrear cada módulo importando datetime y aplicarles parches. Esto es en esencia lo que hace Freezegun . Se necesita un objeto que se burla de la datetime , itera a través de sys.modules para encontrar dónde se ha importado la fecha y reemplaza cada instancia. Supongo que es discutible si puedes hacer esto elegantemente en una función.


[EDITAR]

Tal vez la parte más interesante de esta pregunta es ¿Por qué no puedo parchar somefunction.__call__ ?

Como la función no utiliza el código de __call__ sino __call__ (un objeto de envoltura de método) usa el código de la función.

No encuentro ninguna documentación bien fundada sobre eso, pero puedo probarlo (Python2.7):

>>> def f(): ... return "f" ... >>> def g(): ... return "g" ... >>> f <function f at 0x7f1576381848> >>> f.__call__ <method-wrapper ''__call__'' of function object at 0x7f1576381848> >>> g <function g at 0x7f15763817d0> >>> g.__call__ <method-wrapper ''__call__'' of function object at 0x7f15763817d0>

Reemplace el código de g por el código de g :

>>> f.func_code = g.func_code >>> f() ''g'' >>> f.__call__() ''g''

Por supuesto, las referencias f y f.__call__ no se modifican:

>>> f <function f at 0x7f1576381848> >>> f.__call__ <method-wrapper ''__call__'' of function object at 0x7f1576381848>

Recupere la implementación original y copie __call__ referencias __call__ lugar:

>>> def f(): ... return "f" ... >>> f() ''f'' >>> f.__call__ = g.__call__ >>> f() ''f'' >>> f.__call__() ''g''

Esto no tiene ningún efecto en la función f . Nota: en Python 3 debe usar __code__ lugar de func_code .

Espero que alguien pueda señalarme la documentación que explica este comportamiento.

Tienes una forma de utils : en utils puedes definir

class Utcnow(object): def __call__(self): return datetime.datetime.utcnow() utcnow = Utcnow()

Y ahora tu parche puede funcionar como un amuleto.

Sigue la respuesta original que considero incluso la mejor manera de implementar tus pruebas.

Tengo mi propia regla de oro : nunca aplique parches a los métodos protegidos . En este caso, las cosas son un poco más suaves porque el método protegido se introdujo solo para probar, pero no puedo ver por qué.

El verdadero problema aquí es que no puede parchear datetime.datetime.utcnow directamente (es la extensión C como escribió en el comentario anterior). Lo que puede hacer es aplicar un parche a la datetime y la utcnow , utcnow comportamiento estándar y anulando la función utcnow :

>>> with mock.patch("datetime.datetime", mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=3))): ... print(datetime.datetime.utcnow()) ... 3

Ok eso no es muy claro y ordenado, pero puedes presentar tu propia función como

def mock_utcnow(return_value): return mock.Mock(wraps=datetime.datetime, utcnow=mock.Mock(return_value=return_value)):

y ahora

mock.patch("datetime.datetime", mock_utcnow(***))

haga exactamente lo que necesita sin ninguna otra capa y para cada tipo de importación.

Otra solución puede ser import datetime in utils y parchear ***.utils.datetime ; eso puede darle cierta libertad para cambiar la implementación de referencia de datetime sin cambiar sus pruebas (en este caso, tenga cuidado de cambiar el mock_utcnow() también).


Cuando rastrea __call__ de una función, está configurando el atributo __call__ de esa instancia . Python realmente llama al método __call__ definido en la clase.

Por ejemplo:

>>> class A(object): ... def __call__(self): ... print ''a'' ... >>> a = A() >>> a() a >>> def b(): print ''b'' ... >>> b() b >>> a.__call__ = b >>> a() a >>> a.__call__ = b.__call__ >>> a() a

Asignar algo a a.__call__ tiene sentido.

Sin embargo:

>>> A.__call__ = b.__call__ >>> a() b

TLDR;

a() no llama a.__call__ . Llama a type(a).__call__(a) .

Campo de golf

Hay una buena explicación de por qué sucede eso en respuesta a "¿Por qué type(x).__enter__(x) lugar de x.__enter__() en Python estándar contextlib?" .

Este comportamiento está documentado en la documentación de Python en la búsqueda de métodos especiales .