web-dev-qa-db-ja.com

python unittestを使用した抽象的なテストケース

いくつかのtest_ *メソッドを持つ抽象TestCaseを作成することは可能ですか?しかし、このTestCaseは呼び出されず、それらのメソッドはサブクラスでのみ使用されますか?私はテストスイートに1つの抽象TestCaseを含める予定であり、単一のインターフェイスのいくつかの異なる実装に対してサブクラス化されます。これが、すべてのテストメソッドが内部メソッドの変更の1つだけである理由です。どうすればエレガントな方法でそれを行うことができますか?

49
gruszczy

私はあなたが何をするつもりなのかを完全に理解していませんでした-経験則は「テストを賢くしない」です-そこに書いてください。

しかし、あなたが望んでいることを達成するために、あなたがunittest.TestCaseから継承した場合、あなたがunittest.main()を呼び出すときはいつでも、あなたの「抽象」クラスが実行されます-これはあなたが避けたい状況だと思います。

これを行うだけです。TestCaseではなく「object」から継承する「abstract」クラスを作成します。そして、実際の「具体的な」実装では、多重継承を使用するだけです:unittest.TestCaseと抽象クラスの両方から継承します。

import unittest

class Abstract(object):
    def test_a(self):
        print "Running for class", self.__class__

class Test(Abstract, unittest.TestCase):
    pass

unittest.main()

pdate:継承順序を逆にしました-最初にAbstractを使用して、その定義がTestCaseデフォルトで上書きされないようにします。また、以下のコメントでも指摘されています。

62
jsbueno

多重継承は、主に次の2つの理由から、ここでは優れたオプションではありません。

  1. TestCaseのどのメソッドもsuper()を使用していないため、setUp()tearDown()などのメソッドを機能させるには、最初にクラスをリストする必要があります。
  2. pylintは、その時点でselfで定義されていないself.assertEquals()などを基本クラスが使用していることを警告します。

これが私が思いついたものです:run()を基本クラスのみのノーオペレーションに変換します。

class TestBase( unittest.TestCase ):

  def __init__( self, *args, **kwargs ):
    super( TestBase, self ).__init__( *args, **kwargs )
    self.helper = None
    # Kludge alert: We want this class to carry test cases without being run
    # by the unit test framework, so the `run' method is overridden to do
    # nothing.  But in order for sub-classes to be able to do something when
    # run is invoked, the constructor will rebind `run' from TestCase.
    if self.__class__ != TestBase:
      # Rebind `run' from the parent class.
      self.run = unittest.TestCase.run.__get__( self, self.__class__ )                          
    else:
      self.run = lambda self, *args, **kwargs: None

  def newHelper( self ):
    raise NotImplementedError()

  def setUp( self ):
    print "shared for all subclasses"
    self.helper = self.newHelper()

  def testFoo( self ):
    print "shared for all subclasses"
    # test something with self.helper

class Test1( TestBase ):
  def newHelper( self ):
    return HelperObject1()

class Test2( TestBase ):
  def newHelper( self ):
    return HelperObject2()
13
tsuna

これまで誰もが見逃してきたveryシンプルな方法があります。そして、いくつかの回答とは異なり、それはallテストドライバーで動作します。

通常どおりに継承を使用して、次を追加します。

del AbstractTestCase

モジュールの最後。

8
o11c

私の2セントで言えば、おそらく何らかの慣例に反することになりますが、抽象的なテストケースを保護されたメンバーとして定義して、その実行を防ぐことができます。 Djangoに以下を実装し、必要に応じて機能します。以下の例を参照してください。

from Django.test import TestCase


class _AbstractTestCase(TestCase):

    """
    Abstract test case - should not be instantiated by the test runner.
    """

    def test_1(self):
        raise NotImplementedError()

    def test_2(self):
        raise NotImplementedError()


class TestCase1(_AbstractTestCase):

    """
    This test case will pass and fail.
    """

    def test_1(self):
        self.assertEqual(1 + 1, 2)


class TestCase2(_AbstractTestCase):

    """
    This test case will pass successfully.
    """

    def test_1(self):
        self.assertEqual(2 + 2, 4)

    def test_2(self):
        self.assertEqual(12 * 12, 144)
7
Dan Ward

Run_unittestにすべてのテストクラスを明示的にリストするという規則(たとえば、Pythonテスト規則の多くの使用法についてのテストスイートを参照)に従っている場合、not特定のクラスをリストします。

Unittest.mainを引き続き使用する場合、およびunittest2の使用を許可できる場合(たとえば、Python 2.7から))、その load_tests プロトコルを使用して、どのクラスを指定することができますテストケースを含む)。以前のバージョンでは、TestLoaderをサブクラス化して、 loadTestsFromModule をオーバーライドする必要があります。

4

Pythonユニットテストライブラリには load_tests protocol があり、これを使用して正確に何をしたいのかを達成できます。

# Add this function to module with AbstractTestCase class
def load_tests(loader, tests, _):
    result = []
    for test_case in tests:
        if type(test_case._tests[0]) is AbstractTestCase:
            continue
        result.append(test_case)
    return loader.suiteClass(result)
2
uglide

ミックスインの代わりに継承を使用したい場合、簡単な解決策は、抽象テストを別のクラスにネストすることです。

テストランナーの検出に関する問題を回避し、別のモジュールから抽象テストをインポートできます。

import unittest

class AbstractTests(object):
    class AbstractTest(unittest.TestCase)
        def test_a(self):
            print "Running for class", self.__class__

class Test(AbstractTests.AbstractTest):
    pass
1
jrobichaud

OPが実行していることを実行したいもう1つの理由は、いくつかの環境/シナリオで再現する必要がある一連のコアテストの多くを実装する高度にパラメーター化された基本クラスを作成することです。私が説明しているのは、本質的にはunittestを使用して、パラメーター化されたフィクスチャ(pytest)を作成することです。

あなた(私のような)が多重継承ベースのソリューションからできるだけ早く逃げると決めたとすると、load_tests()を使用して、ロードされたスイートから基本クラスを除外することで、次の問題が発生する可能性があります。

標準のTestLoaderでは、load_testsは後に呼び出されますauto-loading-from-classが実行された後。なぜなら:*このauto-loading-from-classは、標準の署名init(self、name)を使用して、基本クラスからインスタンスを構築しようとします。 *この基本クラスに非常に異なるctorシグネチャを持たせたい場合、または*他の何らかの理由で基本クラスインスタンスの構築後削除をスキップしたい場合

..この基本クラスからのテストインスタンスの自動読み込みを完全に防止したい場合があります。

編集: この他のスレッドでのVadimのソリューション はこれを行うためのよりエレガントで簡潔な独立した方法です。 「ネストされたクラストリック」を実装し、TestLoaderがTestCaseベースを「検索」するのを防ぐ目的で美しく機能することを確認しました。

私はもともと、TestLoader.loadTestsFromModuleを変更して、モジュール内の他のTestCaseクラスの基本クラスとして機能するTestCaseクラスを単にスキップすることでこれを行っていました。

for name in dir(module):
    obj = getattr(module, name)
    # skip TestCase classes:
    # 1. without any test methods defined
    # 2. that are base classes
    #    (we don't allow instantiating TestCase base classes, which allows test designers
    #     to implement actual test methods in highly-parametrized base classes.)
    if isinstance(obj, type) and issubclass(obj, unittest.TestCase) and \
            self.getTestCaseNames(obj) and not isbase(obj, module):
        loaded_suite = self.loadTestsFromTestCase(obj)
        # ignore empty suites
        if loaded_suite.countTestCases():
            tests.append(loaded_suite)

どこ:

def isbase(cls, module):
    '''Returns True if cls is base class to any classes in module, else False.'''
    for name in dir(module):
        obj = getattr(module, name)
        if obj is not cls and isinstance(obj, type) and issubclass(obj, cls):
            return True
    return False

上記で説明したパラメーター化は、各子クラスにフィクスチャーの詳細(パラメーター)を定義し、それらを基本クラスのTestCase ctorに渡して、そのすべての一般的な実装メソッド(「フィクスチャ」のsetUp */tearDown * /)を実装することで実装されます。 cleanup *およびテストメソッド自体)には、その子TestCaseクラスが操作する非常に具体的なフィクスチャを定義するすべての情報があります。

私にとって、これは、チームのテストをpytest asapに移動する予定なので、パラメーター化されたフィクスチャをユニットテストですばやく実装するための一時的な解決策でした。

0
timblaktu

私は以下の方法でそれを行いました、おそらくそれはあなたに刺激を与えることができます:

class AbstractTest(TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def _test_1(self):
        # your test case here

class ConcreteTest(AbstractTest)

    def test_1(self):
        self._test_1()

これは最も便利な解決策ではありませんが、多重継承を回避できます。また、Dan Wardによって提案されたソリューションは、PyCharmでのDjangoテストでは機能しませんでした。

0
Asmox