为什么此测试失败?

时间:2019-09-09 06:11:51

标签: python

添加新的单元测试后,在新测试之后,我开始在不相关的测试运行中失败。我不明白为什么。

我已将情况简化为以下代码。我仍然不知道发生了什么。我很惊讶地注释掉看似无关的代码行会影响结果:isinstance中删除对Block.__init__的调用会更改isinstance(blk, AddonDefault)test_addons的结果

import abc

class Addon:
    pass

class AddonDefault(Addon, metaclass=abc.ABCMeta):
    pass

class Block:
    def __init__(self):
        isinstance(self, CBlock)

class CBlock(Block, metaclass=abc.ABCMeta):
    def __init_subclass__(cls, *args, **kwargs):
        if issubclass(cls, Addon):
            raise TypeError("Do not mix Addons and CBlocks!")
        super().__init_subclass__(*args, **kwargs)

class FBlock(CBlock):
    pass

def test_addons():
    try:
        class CBlockWithAddon(CBlock, AddonDefault):
            pass
    except TypeError:
        pass

    blk = FBlock()
    assert not isinstance(blk, AddonDefault), "TEST FAIL"
    print("OK")

test_addons()

运行python3 test.py时出现TEST FAIL异常。但是FBlock来自CBlock,而Block来自AddonDefault。怎么可能是import io from 'socket.io-client'; const socket = io('http://xx.xxx.xx.xxx:4000'); componentDidMount(){ socket.on('connect', () => { console.log(socket.connected);// connected is showing true }); socket.on('messages', (data) => { // but here nothing happening. it is not coming inside function. console.log(data); }); } 的实例?


更新:我想强调的是,发布的代码的唯一目的是演示我无法理解的行为。它是通过尽可能减少一个更大的程序而创建的。在此过程中,以前存在的任何逻辑都丢失了,因此请按原样进行操作,并集中讨论为什么它给出明显错误的答案的问题。

2 个答案:

答案 0 :(得分:3)

不是完整的答案,但有一些提示。

似乎CBlockWithAddon仍被视为AddonDefault的子类。例如。在您的test_addons()中添加两个打印语句:

def test_addons():
    print(AddonDefault.__subclasses__())
    try:
        class CBlockWithAddon(CBlock, AddonDefault):
            pass
    except TypeError:
        pass

    print(AddonDefault.__subclasses__())
    blk = FBlock()
    assert not isinstance(blk, AddonDefault), "TEST FAIL"
    print("OK")

产生

[]
[<class '__main__.test_addons.<locals>.CBlockWithAddon'>]
...
AssertionError: TEST FAIL

_py_abc tests for this

    # Check if it's a subclass of a subclass (recursive)
    for scls in cls.__subclasses__():
        if issubclass(subclass, scls):
            cls._abc_cache.add(subclass)
            return True

cls=AddonDefaultsubclass=FBlockscls=CBlockWithAddon时,它将返回True。

所以看来有两件事出了错:

  • 创建不正确的CBlockWithAddon仍然被视为AddonDefault的子类。
  • 但是CBlockWithAddon是以某种方式创建的,因此它似乎是FBlock的超类。

也许损坏的CBlockWithAddon实际上与CBlock相同,因此是FBlock的超类。

这对我来说已经足够了。也许可以帮助您进行调查。

(我必须使用import _py_abc as abc进行此分析。这似乎无关紧要。)


Edit1:关于CBlockWithAddon与其超类CBlock类似的直觉似乎是正确的:

CBWA = AddonDefault.__subclasses__()[0]
print(CBWA)
print(CBWA.__dict__.keys())
print(CBlock.__dict__.keys())
print(CBWA._abc_cache is CBlock._abc_cache)

给予

<class '__main__.test_addons.<locals>.CBlockWithAddon'>
dict_keys(['__module__', '__doc__'])
dict_keys(['__module__', '__init_subclass__', '__doc__', '__abstractmethods__', '_abc_registry', '_abc_cache', '_abc_negative_cache', '_abc_negative_cache_version'])
True

因此CBlockWithAddon的创建不正确,例如其缓存注册表设置不正确。因此,访问这些属性将访问(第一个)超类的属性,在这种情况下为CBlock。创建isinstance(self, CBlock)时,不太合适的伪blk将填充缓存,因为FBlock实际上是CBlock的子类。然后,在调用isinstance(blk, AddonDefault)时,此缓存会被错误地重用。

我认为这样可以回答问题。现在,下一个问题将是:为什么CBlockWithAddon在从未正确定义的情况下成为CBlock的子类?


Edit2:更简单的概念证明。

from abc import ABCMeta

class Animal(metaclass=ABCMeta):
    pass

class Plant(metaclass=ABCMeta):
    def __init_subclass__(cls):
        assert not issubclass(cls, Animal), "Plants cannot be Animals"

class Dog(Animal):
    pass

try:
    class Triffid(Animal, Plant):
        pass
except Exception:
    pass

print("Dog is Animal?", issubclass(Dog, Animal))
print("Dog is Plant?", issubclass(Dog, Plant))

将导致

Dog is Animal? True
Dog is Plant? True

请注意,更改打印语句的顺序将导致

Dog is Plant? False
Dog is Animal? False

答案 1 :(得分:0)

为什么要使子类抽象而不是基类? 这背后有某种逻辑吗?

如果将抽象向上移动一层,它将按预期工作,否则将类型和abc元类混合使用:

import abc

class Addon(metaclass=abc.ABCMeta):
    pass

class AddonDefault(Addon):
    pass

class Block(metaclass=abc.ABCMeta):
    def __init__(self):
        isinstance(self, CBlock)

class CBlock(Block):
    def __init_subclass__(cls, *args, **kwargs):
        if issubclass(cls, Addon):
            raise TypeError("Do not mix Addons and CBlocks!")
        super().__init_subclass__(*args, **kwargs)

class FBlock(CBlock):
    pass

def test_addons():
    try:
        class CBlockWithAddon(CBlock, AddonDefault):
            pass
    except TypeError:
        pass

    blk = FBlock()
    assert not isinstance(blk, AddonDefault), "TEST FAIL"
    print("OK")

test_addons()