Python-测试抽象基类

Python-测试抽象基类,python,oop,testing,abc,Python,Oop,Testing,Abc,我正在寻找在抽象基类中定义的测试方法的方法/最佳实践。我可以直接想到的一件事是对基类的所有具体子类执行测试,但有时这似乎有些过分 考虑这个例子: import abc class Abstract(object): __metaclass__ = abc.ABCMeta @abc.abstractproperty def id(self): return @abc.abstractmethod def foo(self):

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

考虑这个例子:

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"

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

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

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

但是,可以创建一个实用函数来内省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)
def混凝土机(abclass):
"""
>>>进口abc
>>>类摘要(元类=abc.ABCMeta):
…@abc.abstractmethod
…def bar(自):
…不返回
>>>c=混凝土工(摘要)
>>>c.(姓名)__
“虚拟的、具体的、抽象的”
>>>c().bar()#doctest:+省略号
(, (), {})
"""
如果不是abclass中的“\uuuuu abstractmethods\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
返回类
new\u dict=abclass.\u dict\uuuu.copy()
对于abclass中的abstractmethods.\uuuu abstractmethods\uuuu:
#用标识函数替换每个abc方法或属性:
新的[abstractmethod]=λx,*args,**kw:(x,args,kw)
#创建一个新类,其中包含重写的ABC:
返回类型(“虚拟的”\u具体的”%s“%abclass.\u名称”\u,(abclass,),新的\u目录)

以下是我的发现:如果将
\uuuAbstractMethods\uuuuu
属性设置为空集,则可以实例化抽象类。这种行为在以下章节中有详细说明:

如果生成的
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

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

>>> import abc
>>> class A(metaclass = abc.ABCMeta):
...     @abc.abstractmethod
...     def foo(self): pass
您不能实例化一个:

>>> A()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class A with abstract methods foo
如果覆盖
\uuuu abstractmethods\uuuu
,则可以:

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

在较新版本的Python中,您可以使用


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

def concreter(abclass):
    class concreteCls(abclass):
        pass
    concreteCls.__abstractmethods__ = frozenset()
    return type('DummyConcrete' + abclass.__name__, (concreteCls,), {})
生成的类仍然具有所有原始的抽象方法(现在可以调用这些方法,即使这不太可能有用……),并且可以根据需要进行模拟。

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

以下是您案例的示例:

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)

嗯..如果是这样的话,用定义的抽象方法和属性为抽象类创建一个伪子类,然后对它进行测试是明智的吗?@bow:是的,你就是这么做的。酷。我将尝试在一些测试中修改此代码:)。谢谢为什么我们真的需要
multiple
?您可以使用
multiple()
一次模拟多个属性,是的,但这似乎不能证明它在这里的使用是正确的,因为只有一个属性看起来是修补过的。有什么更简单的方法可以使用吗?
mocker.patch.object
解决方案:
mocker.patch.object(MyAbcClass,“\uuuuuuAbstractMethods”,new\uCallable=set)
@Regisz甚至:
@patch.object(MyAbcClass,'\uuuAbstractMethods',set())
class MyAbcClassTest(unittest.TestCase):

    @patch.multiple(MyAbcClass, __abstractmethods__=set())
    def test(self):
         self.instance = MyAbcClass() # Ha!
def concreter(abclass):
    class concreteCls(abclass):
        pass
    concreteCls.__abstractmethods__ = frozenset()
    return type('DummyConcrete' + abclass.__name__, (concreteCls,), {})
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)