Python 我可以执行哪些测试以确保正确覆盖了setattr?
我学习Python已经有一段时间了,我逐渐明白正确地重写Python 我可以执行哪些测试以确保正确覆盖了setattr?,python,python-3.x,unit-testing,setattr,Python,Python 3.x,Unit Testing,Setattr,我学习Python已经有一段时间了,我逐渐明白正确地重写\uuu setattr\uuu可能会很麻烦(!) 有哪些有效方法可以确保/向自己证明已正确执行了超控?我特别关心的是确保覆盖与描述符协议和MRO保持一致 (标记为Python3.x,因为我正在使用它,但这个问题当然也适用于其他版本。) “覆盖”显示默认行为的示例代码(但我如何证明它?): 覆盖违反描述符协议的人为示例(实例存储查找发生在描述符查找之前-但如何测试它?): 理想答案将提供一个通用测试,当\uuuu setattr\uuuu被
\uuu setattr\uuu
可能会很麻烦(!)
有哪些有效方法可以确保/向自己证明已正确执行了超控?我特别关心的是确保覆盖与描述符协议和MRO保持一致
(标记为Python3.x,因为我正在使用它,但这个问题当然也适用于其他版本。)
“覆盖”显示默认行为的示例代码(但我如何证明它?):
覆盖违反描述符协议的人为示例(实例存储查找发生在描述符查找之前-但如何测试它?):
理想答案将提供一个通用测试,当
\uuuu setattr\uuuu
被正确覆盖时,该测试将成功,否则将失败 在本例中,有一个简单的解决方案:添加一个绑定描述符,其名称位于mydict
中,并测试分配给该名称是否通过描述符(注意:Python 2.x代码,我这里没有安装Python 3):
如果将重写定义为调用父类的
\uuuuuuu setattr\uuuuu
正确,则可以将方法移植到定义其自定义\uuuuu setattr\uuuuu
的类层次结构中:
def inject_tester_class(cls):
def __setattr__(self, name, value):
self._TesterClass__setattr_args.append((name, value))
super(intermediate, self).__setattr__(name, value)
def assertSetAttrDelegatedFor(self, name, value):
assert \
[args for args in self._TesterClass__setattr_args if args == (name, value)], \
'__setattr__(name, value) was never delegated'
body = {
'__setattr__': __setattr__,
'assertSetAttrDelegatedFor': assertSetAttrDelegatedFor,
'_TesterClass__setattr_args': []
}
intermediate = type('TesterClass', cls.__bases__, body)
testclass = type(cls.__name__, (intermediate,), vars(cls).copy())
# rebind the __class__ closure
def closure():
testclass
osa = testclass.__setattr__
new_closure = tuple(closure.__closure__[0] if n == '__class__' else c
for n, c in zip(osa.__code__.co_freevars, osa.__closure__))
testclass.__setattr__ = type(osa)(
osa.__code__, osa.__globals__, osa.__name__,
osa.__defaults__, new_closure)
return testclass
此函数跳转几圈以插入一个中间类,该类将拦截任何正确委派的\uuuu setattr\uuu
调用。即使除了默认的对象
之外没有任何基类,它也可以工作(这不允许我们替换\uuuuuuu setattr\uuuuuu
,以便更容易地测试它)
它确实假设您正在使用super()。\uuuu setattr\uuuu()
进行委托,而您使用的super()
没有参数。它还假设不涉及任何元类
额外的\uuuu setattr\uuuuu
以与现有MRO一致的方式注入;额外的中间类被注入到原始类和MRO的其余部分之间,并将\uuuuu setattr\uuuu
调用向前委派
要在测试中使用它,您需要使用上述函数生成一个新类,创建一个实例,然后在该实例上设置属性:
MyTestClass = inject_tester_class(MyClass)
my_test_instance = MyTestClass()
my_test_instance.foo = 'bar'
my_test_instance.assertSetAttrDelegatedFor('foo', 'bar')
如果未委派设置
foo
,则会引发AssertionError
异常,该异常被unittest
测试运行程序记录为测试失败。您当前正在测试什么?没有什么特别的。这是一个一般性问题。我正在考虑如何做到这一点,但想不出任何方法(例如为属性设置顺序的每个步骤执行一个log或print语句,以显示它们完成的顺序-不确定如何做到这一点)。但我也很没经验。@RickTeachey:对不起,我看错了。问题是没有调用原始重写的\uuuuu setattr\uuuu
版本。由于从未调用过object.\uuuuu setattr\uuuuu
,因此也不会调用带有setter的描述符。答案都是很好的解决方案,很难在两者之间进行选择。最后,我选择了Bruno's,只是因为它更具体地解决了这个特殊的问题。我已经搜索过了,但找不到关于绑定描述符的更多信息。“绑定描述符”等同于“数据描述符”吗?如果没有,是否存在“非绑定”描述符?另外:我理解上面的测试,这是证明mro被违反的一种非常聪明的方法。是否有一种通用的方法来证明任何给定的实现都没有违反mro?是的,“绑定描述符”的意思与“数据描述符”相同:一种实现\uuuuu set\uuu()
的描述符。请注意/在上面的测试中,它与mro(“方法解析顺序”又名查找继承树)几乎没有关系,因为不涉及继承,它是关于绑定描述符的。好的。那我就把我的术语弄混了。
class MyBindingDescriptor(object):
def __init__(self, key):
self.key = key
def __get__(self, obj, cls=None):
if not obj:
return self
return obj.__dict__[self.key]
def __set__(self, obj, value):
obj.__dict__[self.key] = value
sentinel = object()
class MyClass(object):
test = MyBindingDescriptor("test")
def __init__(self, mydict):
self.__dict__['mydict'] = mydict
self.__dict__["test"] = sentinel
def __setattr__(self, att, val):
if att in self.mydict:
self.mydict[att] = val
else:
super(MyClass, self).__setattr__(att, val)
# first test our binding descriptor
instance1 = MyClass({})
# sanity check
assert instance1.test is sentinel, "instance1.test should be sentinel, got '%s' instead" % instance1.test
# this one should pass ok
instance1.test = NotImplemented
assert instance1.test is NotImplemented, "instance1.test should be NotImplemented, got '%s' instead" % instance1.test
# now demonstrate that the current implementation is broken:
instance2 = MyClass({"test":42})
instance2.test = NotImplemented
assert instance2.test is NotImplemented, "instance2.test should be NotImplemented, got '%s' instead" % instance2.test
def inject_tester_class(cls):
def __setattr__(self, name, value):
self._TesterClass__setattr_args.append((name, value))
super(intermediate, self).__setattr__(name, value)
def assertSetAttrDelegatedFor(self, name, value):
assert \
[args for args in self._TesterClass__setattr_args if args == (name, value)], \
'__setattr__(name, value) was never delegated'
body = {
'__setattr__': __setattr__,
'assertSetAttrDelegatedFor': assertSetAttrDelegatedFor,
'_TesterClass__setattr_args': []
}
intermediate = type('TesterClass', cls.__bases__, body)
testclass = type(cls.__name__, (intermediate,), vars(cls).copy())
# rebind the __class__ closure
def closure():
testclass
osa = testclass.__setattr__
new_closure = tuple(closure.__closure__[0] if n == '__class__' else c
for n, c in zip(osa.__code__.co_freevars, osa.__closure__))
testclass.__setattr__ = type(osa)(
osa.__code__, osa.__globals__, osa.__name__,
osa.__defaults__, new_closure)
return testclass
MyTestClass = inject_tester_class(MyClass)
my_test_instance = MyTestClass()
my_test_instance.foo = 'bar'
my_test_instance.assertSetAttrDelegatedFor('foo', 'bar')