Python - 测试抽象基类

bow*_*bow 32 python oop testing abc

我正在寻找在抽象基类中定义的测试方法的方法/最佳实践.我可以直接想到的一件事是在基类的所有具体子类上执行测试,但这在某些时候似乎过多.

考虑这个例子:

import abc

class Abstract(object):

    __metaclass__ = abc.ABCMeta

    @abc.abstractproperty
    def id(self):
        return   

    @abc.abstractmethod
    def foo(self):
        print "foo"

    def bar(self):
        print "bar"
Run Code Online (Sandbox Code Playgroud)

是否可以在bar不进行任何子类化的情况下进行测试?

Mar*_*mro 34

在较新版本的Python中,您可以使用 unittest.mock.patch()

class MyAbcClassTest(unittest.TestCase):

    @patch.multiple(MyAbcClass, __abstractmethods__=set())
    def test(self):
         self.instance = MyAbcClass() # Ha!
Run Code Online (Sandbox Code Playgroud)

  • @Regisz 甚至:`@patch.object(MyAbcClass, '__abstractmethods__', set())` (5认同)
  • `mocker.patch.object` 解决方案:`mocker.patch.object(MyAbcClass, "__abstractmethods__", new_callable = set)` (3认同)
  • 为什么要使用“set()”?为什么不是“int”或“str”或其他任何东西? (2认同)

jb.*_*jb. 23

这是我发现的:如果将__abstractmethods__属性设置为空集,您将能够实例化抽象类.此行为在PEP 3119中指定:

如果结果__abstractmethods__集非空,则该类被视为抽象,并尝试实例化它将引发TypeError.

因此,您只需要在测试期间清除此属性.

>>> import abc
>>> class A(metaclass = abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self): pass
Run Code Online (Sandbox Code Playgroud)

你无法实例化A:

>>> A()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class A with abstract methods foo
Run Code Online (Sandbox Code Playgroud)

如果你覆盖__abstractmethods__你可以:

>>> A.__abstractmethods__=set()
>>> A() #doctest: +ELLIPSIS
<....A object at 0x...>
Run Code Online (Sandbox Code Playgroud)

它有两种方式:

>>> class B(object): pass
>>> B() #doctest: +ELLIPSIS
<....B object at 0x...>

>>> B.__abstractmethods__={"foo"}
>>> B()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class B with abstract methods foo
Run Code Online (Sandbox Code Playgroud)

您还可以使用unittest.mock(从3.3开始)覆盖临时ABC行为.

>>> class A(metaclass = abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self): pass
>>> from unittest.mock import patch
>>> p = patch.multiple(A, __abstractmethods__=set())
>>> p.start()
{}
>>> A() #doctest: +ELLIPSIS
<....A object at 0x...>
>>> p.stop()
>>> A()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class A with abstract methods foo
Run Code Online (Sandbox Code Playgroud)

  • 说实话,这感觉很老套。 (2认同)

jsb*_*eno 20

正如lunaryon所说,这是不可能的.包含抽象方法的ABCs的目的是它们不像声明的那样可实例化.

但是,可以创建一个内省ABC的实用程序函数,并动态创建一个虚拟的非抽象类.这个函数可以在你的测试方法/函数中直接调用,并且不必在测试文件上使用样板文件代码来测试一些方法.

def concreter(abclass):
    """
    >>> import abc
    >>> class Abstract(metaclass=abc.ABCMeta):
    ...     @abc.abstractmethod
    ...     def bar(self):
    ...        return None

    >>> c = concreter(Abstract)
    >>> c.__name__
    'dummy_concrete_Abstract'
    >>> c().bar() # doctest: +ELLIPSIS
    (<abc_utils.Abstract object at 0x...>, (), {})
    """
    if not "__abstractmethods__" in abclass.__dict__:
        return abclass
    new_dict = abclass.__dict__.copy()
    for abstractmethod in abclass.__abstractmethods__:
        #replace each abc method or property with an identity function:
        new_dict[abstractmethod] = lambda x, *args, **kw: (x, args, kw)
    #creates a new class, with the overriden ABCs:
    return type("dummy_concrete_%s" % abclass.__name__, (abclass,), new_dict)
Run Code Online (Sandbox Code Playgroud)


小智 6

不,这不对。的真正目的abc是创建无法实例化的类,除非用具体实现覆盖所有抽象属性。因此,您需要从抽象基类派生并重写所有抽象方法和属性。

  • @bow:是的,你就是这样做的。 (3认同)

小智 6

也许@jsbueno 提出的更紧凑的具体版本可以是:

def concreter(abclass):
    class concreteCls(abclass):
        pass
    concreteCls.__abstractmethods__ = frozenset()
    return type('DummyConcrete' + abclass.__name__, (concreteCls,), {})
Run Code Online (Sandbox Code Playgroud)

生成的类仍然具有所有原始抽象方法(现在可以调用,即使这不太可能有用......)并且可以根据需要进行模拟。


meh*_*hdi 6

您可以使用多重继承实践来访问抽象类的已实现方法。显然,遵循这样的设计决策取决于抽象类的结构,因为您需要在测试用例中实现抽象方法(至少带有签名)。

这是您的案例的示例:

class Abstract(object):

    __metaclass__ = abc.ABCMeta

    @abc.abstractproperty
    def id(self):
        return

    @abc.abstractmethod
    def foo(self):
        print("foo")

    def bar(self):
        print("bar")

class AbstractTest(unittest.TestCase, Abstract):

    def foo(self):
        pass
    def test_bar(self):
        self.bar()
        self.assertTrue(1==1)
Run Code Online (Sandbox Code Playgroud)