60

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

4

12 に答える 12

76

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

しかし、必要なことを達成するために、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()

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

于 2010-12-30T23:12:56.273 に答える
19

これまで誰もが見逃していた非常に簡単な方法があります。また、いくつかの回答とは異なり、テストドライバーを切り替えるとすぐに失敗するのではなく、すべてのテストドライバーで機能します。

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

del AbstractTestCase

モジュールの最後に。

于 2017-04-11T18:31:14.140 に答える
13

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

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

これが私が思いついた恨みです: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()
于 2013-08-04T19:15:59.940 に答える
11

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

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

import unittest

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

class Test(AbstractTests.AbstractTest):
    pass
于 2018-05-04T13:38:18.937 に答える
9

私の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)
于 2014-09-05T10:29:26.217 に答える
7

unittest.SkipTestで育つsetUpClass()

もう1つのアプローチは、基本クラスのunittest.SkipTestinを上げ、子クラスsetUpClass()でオーバーライドすることです。setUpClass()

class BaseTestCase(TestCase):
    @classmethod
    def setUpClass(cls):
        "Child classes must override this method and define cls.x and cls.y"
        raise unittest.SkipTest

    def test_x(self):
        self.assertEqual(self.x * 3, self.x)

    def test_y(self):
        self.assertEqual(self.y * 3, self.y + self.y + self.y)

    def test_z(self):
        self.assertEqual(self.x + self.y, self.y)


class IntegerTestCase(BaseTestCase):
    @classmethod
    def setUpClass(cls):
        cls.x = 0
        cls.y = 2


class StringTestCase(BaseTestCase):
    @classmethod
    def setUpClass(cls):
        cls.x = ''
        cls.y = 'zuzuka'

独自のテストケースを定義するカスタムTestCaseを使用する必要がsetUpClass()あり、を呼び出す必要があるsuper().setUpClass()場合は、独自のメソッドを定義して「データを設定」し、そのメソッド内でのみSkipTestを発生させることができます。

class BaseTestCase(ThidPartyTestCase):
    @classmethod
    def setUpClass(cls):
        super().setUpClass()  # if ThirdPartyTestCase has own setUpClass()
        cls.setUpTestCaseData()

    @classmethod
    def setUpTestCaseData(cls):
        "Override and set up cls.x and cls.y here"
        raise unittest.SkipTest

    ...  # tests


class IntegerTestCase(BaseTestCase):
    @classmethod
    def setUpTestCaseData(cls):
        cls.x = 0
        cls.y = 2
于 2020-01-02T10:36:20.817 に答える
4

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

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

于 2010-12-30T23:05:44.387 に答える
3

Pythonユニットテストライブラリにはload_testsプロトコルがあり、これを使用して目的を正確に達成できます。

# 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)
于 2015-06-16T10:31:14.973 に答える
3

このunittestモジュールには、テストをスキップするためのいくつかのオプションがあります。

私の推奨する解決策はsetUpClass、「abstract」基本クラスのメソッドをオーバーライドしてunittest.SkipTest、必要に応じて例外を発生させることです。

class BaseTestCase(unittest.TestCase):
  @classmethod
  def setUpClass(cls):
    if cls is BaseTestCase:
      raise unittest.SkipTest("%s is an abstract base class" % cls.__name__)
    else:
      super(BaseTestCase, cls).setUpClass()
于 2020-06-09T23:33:33.713 に答える
1

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

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

標準のTestLoaderでは、クラスからの自動ロードが実行された後にload_testsが呼び出されます。理由:*この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に渡して、すべての一般的なimplメソッド(「フィクスチャ」メソッドsetUp * / tearDown * / cleanup *テストメソッド自体)には、その子TestCaseクラスが操作する非常に具体的なフィクスチャを定義するすべての情報が含まれています。

私にとって、これは、ユニットテストでいくつかのパラメーター化されたフィクスチャをすばやく実装するための一時的なソリューションでした。チームのテストをできるだけ早くpytestに移行する予定だからです。

于 2018-10-19T19:08:50.247 に答える
0

これは、一般的なテストがTestCaseから継承できるようにする(タイプチェックとIDEツールが満足できる)比較的単純なアプローチであり、文書化された単体テスト機能のみを使用し、「スキップ」テストステータスを回避します。

import unittest

class CommonTestCases(unittest.TestCase):
    def __init__(self, methodName='runTest'):
        if self.__class__ is CommonTestCases:
            # don't run these tests on the abstract base implementation
            methodName = 'runNoTestsInBaseClass'
        super().__init__(methodName)

    def runNoTestsInBaseClass(self):
        print('not running tests in abstract base class')
        pass

    def test_common(self):
        # This will run *only* in subclasses. Presumably, this would 
        # be a test you need to repeat in several different contexts.
        self.assertEqual(2 + 2, 4)


class SomeTests(CommonTestCases):
    # inherited test_common *will* be run here

    def test_something(self):
        self.assertTrue(True)


# Also plays nicely with MRO, if needed:
class SomeOtherTests(CommonTestCases, django.test.SimpleTestCase):
    # inherited test_common *will* be run here

    def test_something_else(self):
        self.client.get('/')  # ...

仕組み:unittest.TestCaseドキュメントによると、「TestCaseの各インスタンスは、methodNameという名前のメソッドという単一の基本メソッドを実行します。」デフォルトの「runTests」は、クラスですべてのtest *メソッドを実行します。これが、TestCaseインスタンスが通常機能する方法です。ただし、抽象基本クラス自体で実行する場合は、何もしないメソッドでその動作を単純にオーバーライドできます。

副作用として、テスト数が1つ増えます。つまり、runNoTestsInBaseClass "test"は、CommonTestCasesで実行されると、成功したテストとしてカウントされます。

于 2020-07-26T22:18:05.980 に答える
-1

私はそれを次のように行いました、多分それはあなたにインスピレーションを与えることができます:

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テストでは機能しませんでした。

于 2020-02-23T14:12:03.063 に答える