在比较2个模拟对象

时间:2017-09-07 06:53:45

标签: python class datetime mocking pypy

我正在为我的python库做一些测试,它应该适用于所有python版本--27,33,34,35,36和 pypy 。在我的测试中,我嘲笑datetime库并且它们都有效,除非我在pypy中运行它们(看看为什么我之前加粗了?这样的情节扭曲!)。

这是我遇到麻烦的MCVE:

import mock
import datetime as dtl
mk = mock.Mock(wraps=dtl.datetime)
p = mock.patch('datetime.datetime', mk)
p.start()
from datetime import datetime
d1 = datetime.now()
d2 = datetime.now()
print d1 == d2

在所有python版本中,最后一行返回False。在pypy最后一行抛出:

>>>> d1 == d2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/pypy/lib_pypy/datetime.py", line 1764, in __eq__
    if isinstance(other, datetime):
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

我挖掘了pypy的源代码,试图理解这个问题,为什么它和其他python版本之间存在差异,我发现了以下内容:

# Comparisons of datetime objects with other.

def __eq__(self, other):
    if isinstance(other, datetime):
        return self._cmp(other) == 0
    elif hasattr(other, "timetuple") and not isinstance(other, date):
        return NotImplemented
    else:
        return False

这种有道理。 datetime类现在是Mock对象 - 而python坚持认为isinstance的第二个参数是一个类型。 NEATO。

所以我决定查看cpython对datetime的实现,并惊喜:

# Comparisons of datetime objects with other.

def __eq__(self, other):
    if isinstance(other, datetime):
        return self._cmp(other, allow_mixed=True) == 0
    elif not isinstance(other, date):
        return NotImplemented
    else:
        return False

这里完成了相同的确切验证,但它没有提出任何内容¯\_(ツ)_/¯

我将在python 2.7中添加:

>>> d = datetime.now()
>>> d
datetime.datetime(2017, 9, 7, 9, 31, 50, 838155)
>>> d == d
True
>>> datetime
<Mock id='139788521555024'>
>>> isinstance(d, datetime)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: isinstance() arg 2 must be a class, type, or tuple of classes and types

我有两个问题:

  1. 为什么它适用于cpython?
  2. 如何在不重新实现__eq__或任何其他魔术方法的情况下模拟日期时间对象:)
  3. 非常有责任!

2 个答案:

答案 0 :(得分:1)

这是python中的一个真正的问题,为什么使用isinstanceof这是一个不好的做法。

解决方案是不使用mock进行修补,而是使用继承自datetime.datetime的类进行修补。但是,因为你将从datetime继承而仍想根据真正的datetime类检查类型,我们需要覆盖真实类的isinstance检查。

import datetime as dtl
import mock

real_datetime_class = dtl.datetime

class DatetimeSubclassMeta(type):
    """Datetime mock metaclass to check instancechek to the real class."""

    @classmethod
    def __instancecheck__(mcs, obj):
        return isinstance(obj, real_datetime_class)

class BaseMockedDatetime(real_datetime_class):
    """Mock class to cover datetime class."""

MockedDatetime = DatetimeSubclassMeta('datetime',
                                      (BaseMockedDatetime,),
                                      {})

p = mock.patch('datetime.datetime', MockedDatetime)
p.start()


from datetime import datetime
d = datetime.now()

isinstance(d, datetime)

答案 1 :(得分:0)

至于“为什么”,它可能与CPython中的C实现datetime,PyPy中的纯python以及Python类对象和内置C语言之间的一些细微差别有关。