2012-03-18 54 views
20

抽象基本クラスで定義されたメソッドをテストする方法/ベストプラクティスを探しています。私が直接考えることのできることの1つは、基本クラスのすべての具象サブクラスでテストを実行することですが、それは時には過度に思われます。Python - 抽象基本クラスをテストする

は、この例を考えてみましょう:

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をテストすることは可能ですか?

答えて

15

lunaryonが適切に書いたように、それは不可能です。抽象メソッドを含むABCの目的は、宣言されたとおりにインスタンス化できないことです。

ただし、ABCをイントロスペクションするユーティリティ関数を作成し、ダミーの非抽象クラスをその場で作成することは可能です。この関数はテストメソッド/関数の中で直接呼び出すことができ、いくつかのメソッドをテストするためだけにテストファイルにボイラープレートコードを追加する必要がありません。あなたは抽象クラスをインスタンス化することができるでしょう空のセットであることを__abstractmethods__属性を設定した場合:ここで

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) 
+0

Cool。私はいくつかのテストでこのコードを試してみる:)。ありがとう! – bow

3

いいえ、そうではありません。 abcの目的は、すべての抽象属性が具体的な実装でオーバーライドされない限り、インスタンス化できないクラスを作成することです。したがって、抽象基本クラスから派生し、すべての抽象メソッドとプロパティをオーバーライドする必要があります。

+0

Hm..ifなので、抽象メソッドとプロパティを定義した抽象クラスの偽のサブクラスを作成し、その上でテストを行うのは賢明でしょうか? – bow

+3

@bow:はい、そうです。 – lunaryorn

15

は、私が発見したものです。この動作はPEP 3119に指定されています。

結果__abstractmethods__セットが空でない場合、クラスが抽象的と考えられ、それはTypeErrorを送出しますインスタンス化しようとしています。

この属性をクリアする必要があるのは、テストの期間だけです。

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

あなたは__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 

ます。またを使用することができます(3.3から)一時的なABCの動作を無効にします。

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

結果のクラス:あなたはおそらく、@jsbuenoによって提案されたconcreterのよりコンパクトなバージョンがあることができunittest.mock.patch()

class MyAbcClassTest(unittest.TestCase): 

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

まだ元の抽象メソッドをすべて持っています(これは有用であるとは限りませんが、現在は呼び出すことができます)。そして、nとして嘲笑することができます去った。

+0

本当に 'マルチプル 'が本当に必要なのですか? –

+0

'multiple()'で複数のプロパティを同時にモックすることができます –

+0

はい、1つの属性だけがパッチされているようだから、ここでの使用を正当化するようには思えません。使用できるより簡単なものがありますか? –

2

を使用することができるのPythonの新しいバージョンで

>>> 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 
関連する問題