我正在寻找在抽象基类中定义的测试方法的方法/最佳实践。我可以直接想到的一件事是在基类的所有具体子类上执行测试,但这在某些时候似乎过度。
考虑这个例子:
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
?
答案 0 :(得分:29)
在较新版本的Python中,您可以使用unittest.mock.patch()
class MyAbcClassTest(unittest.TestCase):
@patch.multiple(MyAbcClass, __abstractmethods__=set())
def test(self):
self.instance = MyAbcClass() # Ha!
答案 1 :(得分:19)
以下是我发现的内容:如果将__abstractmethods__
属性设置为空集,则可以实例化抽象类。此行为在PEP 3119:
如果生成的
__abstractmethods__
集非空,则该类被视为抽象,并尝试实例化它将引发TypeError。
因此,您只需要在测试期间清除此属性。
>>> import abc
>>> class A(metaclass = abc.ABCMeta):
... @abc.abstractmethod
... def foo(self): pass
你无法实例化A:
>>> A()
Traceback (most recent call last):
TypeError: Can't instantiate abstract class A with abstract methods foo
如果您覆盖__abstractmethods__
,则可以:
>>> A.__abstractmethods__=set()
>>> A() #doctest: +ELLIPSIS
<....A object at 0x...>
它有两种方式:
>>> 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
您还可以使用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
答案 2 :(得分:18)
正如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)
答案 3 :(得分:3)
不,不是。 abc
的目的是创建无法实例化的类,除非所有抽象属性都被具体实现覆盖。因此,您需要从抽象基类派生并覆盖所有抽象方法和属性。
答案 4 :(得分:2)
@jsbueno提出的 concreter 的更紧凑版本可能是:
def concreter(abclass):
class concreteCls(abclass):
pass
concreteCls.__abstractmethods__ = frozenset()
return type('DummyConcrete' + abclass.__name__, (concreteCls,), {})
结果类仍然具有所有原始抽象方法(现在可以调用,即使这可能不太有用......)并且可以根据需要进行模拟。
答案 5 :(得分:0)
您可以使用multiple inheritance练习来访问抽象类的已实现方法。显然,遵循这样的设计决策取决于抽象类的结构,因为您需要在测试用例中实现抽象方法(至少带上签名)。
这是您的案例示例:
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)