Python - 测试抽象基类

时间:2021-11-26 06:46:10

I am looking for ways / best practices on testing methods defined in an abstract base class. One thing I can think of directly is performing the test on all concrete subclasses of the base class, but that seems excessive at some times.

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

Consider this example:

考虑这个例子:

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"

Is it possible to test bar without doing any subclassing?

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

6 个解决方案

#1


16  

As properly put by lunaryon, it is not possible. The very purpose of ABCs containing abstract methods is that they are not instantiatable as declared.

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

However, it is possible to create a utility function that introspects an ABC, and creates a dummy, non abstract class on the fly. This function could be called directly inside your test method/function and spare you of having to wite boiler plate code on the test file just for testing a few methods.

但是,可以创建一个内省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)

#2


18  

Here is what I have found: If you set __abstractmethods__ attribute to be an empty set you'll be able to instantiate abstract class. This behaviour is specified in PEP 3119:

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

If the resulting __abstractmethods__ set is non-empty, the class is considered abstract, and attempts to instantiate it will raise TypeError.

如果生成的__abstractmethods__集合为非空,则该类被视为抽象,并尝试实例化它将引发TypeError。

So you just need to clear this attribute for the duration of tests.

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

>>> import abc
>>> class A(metaclass = abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self): pass

You cant instantiate A:

你无法实例化A:

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

If you override __abstractmethods__ you can:

如果您覆盖__abstractmethods__,您可以:

>>> A.__abstractmethods__=set()
>>> A() #doctest: +ELLIPSIS
<....A object at 0x...>

It works both ways:

它有两种方式:

>>> 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

You can also use unittest.mock (from 3.3) to override temporarily ABC behaviour.

您还可以使用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

#3


18  

In newer versions of Python you can use unittest.mock.patch()

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

class MyAbcClassTest(unittest.TestCase):

    @patch.multiple(MyAbcClass, __abstractmethods__=set())
    def test(self):
         self.instance = MyAbcClass() # Ha!

#4


3  

No, it's not. The very purpose of abc is to create classes that cannot be instantiated unless all abstract attributes are overridden with concrete implementations. Hence you need to derive from the abstract base class and override all abstract methods and properties.

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

#5


2  

Perhaps a more compact version of the concreter proposed by @jsbueno could be:

也许@jsbueno提出的更紧凑的混凝土版本可能是:

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

The resulting class still has all original abstract methods (which can be now called, even if this is not likely to be useful...) and can be mocked as needed.

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

#6


0  

You can use multiple inheritance practice to have access to the implemented methods of the abstract class. Obviously following such design decision depends on the structure of the abstract class since you need to implement abstract methods (at least bring the signature) in your test case.

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

Here is the example for your case:

以下是您案例的示例:

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)

#1


16  

As properly put by lunaryon, it is not possible. The very purpose of ABCs containing abstract methods is that they are not instantiatable as declared.

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

However, it is possible to create a utility function that introspects an ABC, and creates a dummy, non abstract class on the fly. This function could be called directly inside your test method/function and spare you of having to wite boiler plate code on the test file just for testing a few methods.

但是,可以创建一个内省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)

#2


18  

Here is what I have found: If you set __abstractmethods__ attribute to be an empty set you'll be able to instantiate abstract class. This behaviour is specified in PEP 3119:

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

If the resulting __abstractmethods__ set is non-empty, the class is considered abstract, and attempts to instantiate it will raise TypeError.

如果生成的__abstractmethods__集合为非空,则该类被视为抽象,并尝试实例化它将引发TypeError。

So you just need to clear this attribute for the duration of tests.

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

>>> import abc
>>> class A(metaclass = abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self): pass

You cant instantiate A:

你无法实例化A:

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

If you override __abstractmethods__ you can:

如果您覆盖__abstractmethods__,您可以:

>>> A.__abstractmethods__=set()
>>> A() #doctest: +ELLIPSIS
<....A object at 0x...>

It works both ways:

它有两种方式:

>>> 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

You can also use unittest.mock (from 3.3) to override temporarily ABC behaviour.

您还可以使用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

#3


18  

In newer versions of Python you can use unittest.mock.patch()

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

class MyAbcClassTest(unittest.TestCase):

    @patch.multiple(MyAbcClass, __abstractmethods__=set())
    def test(self):
         self.instance = MyAbcClass() # Ha!

#4


3  

No, it's not. The very purpose of abc is to create classes that cannot be instantiated unless all abstract attributes are overridden with concrete implementations. Hence you need to derive from the abstract base class and override all abstract methods and properties.

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

#5


2  

Perhaps a more compact version of the concreter proposed by @jsbueno could be:

也许@jsbueno提出的更紧凑的混凝土版本可能是:

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

The resulting class still has all original abstract methods (which can be now called, even if this is not likely to be useful...) and can be mocked as needed.

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

#6


0  

You can use multiple inheritance practice to have access to the implemented methods of the abstract class. Obviously following such design decision depends on the structure of the abstract class since you need to implement abstract methods (at least bring the signature) in your test case.

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

Here is the example for your case:

以下是您案例的示例:

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)