我可以执行哪些测试来确保正确覆盖__setattr__?

时间:2014-12-10 11:12:00

标签: python python-3.x unit-testing setattr

我一直在研究Python一段时间,我已经明白正确覆盖__setattr__可能很麻烦(to say the least!)。

有哪些有效方法可以确保/证明覆盖已正确完成?我特别关注确保覆盖与描述符协议和MRO保持一致。

(标记为Python 3.x因为我正在使用它,但问题当然也适用于其他版本。)

“覆盖”表现出默认行为的示例代码(但我该如何证明?):

class MyClass():
    def __setattr__(self,att,val):
        print("I am exhibiting default behavior!")
        super().__setattr__(att,val)

覆盖违反描述符协议的实例(实例存储查找在描述符查找之前发生 - 但我该如何测试呢?):

class MyClass():
    def __init__(self,mydict):
        self.__dict__['mydict'] = mydict
    @property
    def mydict(self):
        return self._mydict
    def __setattr__(self,att,val):
        if att in self.mydict:
            self.mydict[att] = val
        else:
            super().__setattr__(att, val)

理想的答案将提供一个通用测试,当__setattr__被正确覆盖时,该测试将成功,否则将失败。

2 个答案:

答案 0 :(得分:3)

在这种情况下,有一个简单的解决方案:添加一个名称在mydict的绑定描述符,并测试分配给该名称的是通过描述符(NB:Python 2.x代码,我没有这里安装了Python 3):

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

答案 1 :(得分:2)

如果您正确定义覆盖__setattr__ 调用父类的__setattr__ ,那么您可以将方法移植到定义自己的自定义{{1 }}:

__setattr__

此函数跳过一些箍以插入一个中间类,该中级类将拦截任何正确委派的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 调用。即使您没有默认__setattr__以外的任何基类,它也会起作用(我们不会让我们替换object以获得更简单的测试方法)

它确实假设您使用__setattr__进行委派,而您使用super().__setattr__()而不使用参数。它还假设没有涉及元类。

额外super()以与现有MRO一致的方式注入;额外的中间类在原始类和MRO的其余部分之间注入,并从此委托__setattr__调用。

要在测试中使用它,您需要使用上述函数生成一个新类,创建一个实例,然后在该实例上设置属性:

__setattr__

如果未设置MyTestClass = inject_tester_class(MyClass) my_test_instance = MyTestClass() my_test_instance.foo = 'bar' my_test_instance.assertSetAttrDelegatedFor('foo', 'bar') 设置,则会引发foo异常,AssertionError测试运行器会将其记录为测试失败。