基本クラスとサブクラスでのPython単体テスト


148

現在、いくつかの共通のテストを共有する単体テストがあります。次に例を示します。

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

上記の出力は次のとおりです。

Calling BaseTest:testCommon
.Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

最初のものtestCommonが呼び出されないように上記を書き換える方法はありますか?

編集: 上記の5つのテストを実行する代わりに、4つのテストのみを実行します。2つはSubTest1から、もう2つはSubTest2からです。Pythonユニットテストは元のBaseTestを単独で実行しているようで、それが起こらないようにするメカニズムが必要です。


誰も言及していないようですが、主要部分を変更して、BaseTestのすべてのサブクラスを持つテストスイートを実行するオプションはありますか?
KONサイケ

回答:


154

多重継承を使用して、一般的なテストを含むクラス自体がTestCaseを継承しないようにします。

import unittest

class CommonTests(object):
    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(unittest.TestCase, CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(unittest.TestCase, CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

1
これは、これまでで最もエレガントなソリューションです。
ティエリーラム、

27
このメソッドは、基本クラスの順序を逆にした場合にのみ、setUpメソッドとtearDownメソッドで機能します。メソッドはunittest.TestCaseで定義されていて、super()を呼び出さないため、CommonTestsのsetUpメソッドとtearDownメソッドはMROの最初にある必要があります。そうしないと、まったく呼び出されません。
Ian Clelland

32
Ian Clellandの発言を明確にして、私のような人々にとってより明確になるようにします。クラスにメソッドを追加setUpし、派生クラスの各テストでそれらを呼び出す場合は、基本クラスの順序を逆にする必要があります。そのため、次のようになります。tearDownCommonTestsclass SubTest1(CommonTests, unittest.TestCase)
Dennis Golomazov 2013

6
私はこのアプローチのファンではありません。これにより、クラスがunittest.TestCase との 両方から継承する必要のある規約がコードに確立されCommonTestsます。私は、setUpClass以下の方法が最良であり、人為的エラーが発生しにくいと思います。それか、BaseTestクラスをコンテナークラスでラップするか、少しハックですが、テスト実行の印刷出力でスキップメッセージを回避します。
David Sanders

10
この問題CommonTestsは、クラスに存在しないメソッドを呼び出しているため、pylintに適合していることです。
MadScientist 2015

145

多重継承を使用しないでください。後で噛みます。

代わりに、基本クラスを別のモジュールに移動するか、空のクラスでラップすることができます。

class BaseTestCases:

    class BaseTest(unittest.TestCase):

        def testCommon(self):
            print('Calling BaseTest:testCommon')
            value = 5
            self.assertEqual(value, 5)


class SubTest1(BaseTestCases.BaseTest):

    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTestCases.BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

出力:

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK

6
これは私のお気に入りです。これは最小のハック手段であり、メソッドのオーバーライドを妨害せず、MROを変更せず、基本クラスでsetUp、setUpClassなどを定義できます。
Hannes

6
私は真剣にそれを得ません(魔法はどこから来るのですか?)が、私によるとそれははるかに最良の解決策です:) Javaから来て、私は多重継承が嫌いです...
Edouard Berthe

4
@Edouardb unittestは、TestCaseから継承するモジュールレベルのクラスのみを実行します。ただし、BaseTestはモジュールレベルではありません。
JoshB

非常によく似た方法として、呼び出されたときにABCを返す引数なしの関数内にABCを定義することができます
アナカンド

34

この問題は、1つのコマンドで解決できます。

del(BaseTest)

したがって、コードは次のようになります。

import unittest

class BaseTest(unittest.TestCase):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

del(BaseTest)

if __name__ == '__main__':
    unittest.main()

3
BaseTestは、定義中のモジュールのメンバーであるため、SubTestsの基本クラスとして使用できます。定義が完了する直前に、del()はそれをメンバーとして削除するため、ユニットテストフレームワークはモジュールでTestCaseサブクラスを検索するときにそれを見つけません。
mhsmith

3
これは素晴らしい答えです!@MatthewMarshallよりも気に入っています。彼のソリューションでは、self.assert*メソッドが標準オブジェクトに存在しないため、pylintから構文エラーが発生するからです。
SimplyKnownAsG

1
:メソッドのオーバーライドで)(スーパー呼び出すときBaseTestは、例えば、ベースクラスまたはそのサブクラスのどこにも参照されている場合、作業はしません super( BaseTest, cls ).setUpClass( )
ハンネス

1
@Hannes少なくともPython 3では、サブクラスBaseTestを通じて、super(self.__class__, self)またはsuper()サブクラス内でのみ参照できますが、コンストラクタを継承する場合はそうではありません。たぶん、基本クラスがそれ自体を参照する必要があるときに、そのような「匿名の」代替手段もあるでしょう(クラスがそれ自体を参照する必要があるとき、私には何の考えもありません)。
スタイン

28

マシュー・マーシャルの答えはすばらしいですが、テストケースごとに2つのクラスから継承する必要があり、エラーが発生しやすくなります。代わりに、これを使用します(python> = 2.7):

class BaseTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        if cls is BaseTest:
            raise unittest.SkipTest("Skip BaseTest tests, it's a base class")
        super(BaseTest, cls).setUpClass()

3
それは素晴らしいです。スキップを使用する必要があることを回避する方法はありますか?私にとって、スキップは望ましくなく、現在のテスト計画(コードまたはテストのいずれか)の問題を示すために使用されますか?
ザックヤング

@ZacharyYoungわからない。他の回答が役立つかもしれません。
Dennis Golomazov 2014

@ZacharyYoung私はこの問題を修正しようとしました、私の答えを見てください。
simonzack 14

2つのクラスから継承することで本質的にエラーが発生しやすいものはすぐにはわかりません
jwg

@jwg受け入れられた回答へのコメントを参照してください:) 2つの基本クラスから各テストクラスを継承する必要があります。それらの正しい順序を維持する必要があります。別の基本テストクラスを追加したい場合は、それからも継承する必要があります。ミックスインに問題はありませんが、この場合、単純なスキップで置き換えることができます。
Dennis Golomazov 2015

7

何を達成しようとしていますか?共通のテストコード(アサーション、テンプレートテストなど)がある場合は、接頭辞が付いていないメソッドに配置して、ロードしないtestようunittestにします。

import unittest

class CommonTests(unittest.TestCase):
      def common_assertion(self, foo, bar, baz):
          # whatever common code
          self.assertEqual(foo(bar), baz)

class BaseTest(CommonTests):

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(CommonTests):

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)

class SubTest2(CommonTests):

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

1
あなたの提案では、サブクラスをテストするときにcommon_assertion()はまだ自動的に実行されますか?
スチュワート

@Stewartいいえ、そうではありません。デフォルト設定では、「test」で始まるメソッドのみが実行されます。
CS

6

マシューの答えは、私がまだ2.5を使っているので、私が使用する必要があった答えです。ただし、2.7以降では、スキップするテストメソッドで@ unittest.skip()デコレータを使用できます。

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

基本型をチェックするには、独自のスキップデコレータを実装する必要があります。以前にこの機能を使用したことはありませんが、頭上からBaseTestをマーカータイプとして使用してスキップを調整できます。

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func

5

これを解決するために私が考えた方法は、基本クラスが使用されている場合にテストメソッドを非表示にすることです。これにより、テストがスキップされないため、多くのテストレポートツールでテスト結果が黄色ではなく緑色になる場合があります。

mixinメソッドと比較して、PyCharmのようなideは、単体テストメソッドが基本クラスから欠落していると文句を言うことはありません。

基本クラスがこのクラスを継承する場合、setUpClassおよびtearDownClassメソッドをオーバーライドする必要があります。

class BaseTest(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        cls._test_methods = []
        if cls is BaseTest:
            for name in dir(cls):
                if name.startswith('test') and callable(getattr(cls, name)):
                    cls._test_methods.append((name, getattr(cls, name)))
                    setattr(cls, name, lambda self: None)

    @classmethod
    def tearDownClass(cls):
        if cls is BaseTest:
            for name, method in cls._test_methods:
                setattr(cls, name, method)
            cls._test_methods = []

5

あなたは追加することができます__test_ = FalseBaseTestクラスで、しかし、あなたはそれを追加した場合、あなたが追加しなければならないことに注意して__test__ = Trueテストを実行できるようにする派生クラスで。

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

    def testCommon(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)

class SubTest1(BaseTest):
    __test__ = True

    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    __test__ = True

    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

if __name__ == '__main__':
    unittest.main()

4

別のオプションは実行しないことです

unittest.main()

その代わりにあなたは使うことができます

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass)
unittest.TextTestRunner(verbosity=2).run(suite)

したがって、クラスのテストのみを実行します TestClass


これは最小のハックソリューションです。unittest.main()収集したものをデフォルトのスイートに変更する代わりに、明示的なスイートを形成してそのテストを実行します。
zgoda

1

@Vladim P.(https://stackoverflow.com/a/25695512/2451329)とほぼ同じですが、少し変更しました。

import unittest2


from some_module import func1, func2


def make_base_class(func):

    class Base(unittest2.TestCase):

        def test_common1(self):
            print("in test_common1")
            self.assertTrue(func())

        def test_common2(self):
            print("in test_common1")
            self.assertFalse(func(42))

    return Base



class A(make_base_class(func1)):
    pass


class B(make_base_class(func2)):

    def test_func2_with_no_arg_return_bar(self):
        self.assertEqual("bar", func2())

そこに行きます。


1

Python 3.2以降では、test_loader関数をモジュールに追加して、テスト検出メカニズムによって検出されるテスト(存在する場合)を制御できます。

たとえば、次のコードでは、元の投稿者SubTest1SubTest2テストケースのみが読み込まれ、無視されBaseます。

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

standard_testsTestSuiteデフォルトのローダーが見つけたテストを含む)を反復しBasesuite代わりにコピーすることは可能であるべきですが、の入れ子の性質によりTestSuite.__iter__、それははるかに複雑になります。


0

testCommonメソッドの名前を別の名前に変更するだけです。Unittestは(通常) 'test'がないものをスキップします。

すばやく簡単

  import unittest

  class BaseTest(unittest.TestCase):

   def methodCommon(self):
       print 'Calling BaseTest:testCommon'
       value = 5
       self.assertEquals(value, 5)

  class SubTest1(BaseTest):

      def testSub1(self):
          print 'Calling SubTest1:testSub1'
          sub = 3
          self.assertEquals(sub, 3)


  class SubTest2(BaseTest):

      def testSub2(self):
          print 'Calling SubTest2:testSub2'
          sub = 4
          self.assertEquals(sub, 4)

  if __name__ == '__main__':
      unittest.main()`

2
これは、どちらのサブテストでもmethodCommonテストを実行しないという結果になります。
Pepper Lebeck-Jobe 2015年

0

これは古いスレッドのようなものですが、今日この問題に遭遇し、自分のハックを考えました。基本クラスを介してアクセスされると、関数の値をNoneにするデコレータを使用します。ベースクラスにテストがない場合は実行されないため、セットアップとセットアップクラスについて心配する必要はありません。

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

    def testSub2(self):
        print('Calling SubTest2:testSub2')
        sub = 4
        self.assertEqual(sub, 4)

if __name__ == '__main__':
    unittest.main()

-2

BaseTestメソッド名をsetUpに変更します。

class BaseTest(unittest.TestCase):
    def setUp(self):
        print 'Calling BaseTest:testCommon'
        value = 5
        self.assertEquals(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print 'Calling SubTest1:testSub1'
        sub = 3
        self.assertEquals(sub, 3)


class SubTest2(BaseTest):
    def testSub2(self):
        print 'Calling SubTest2:testSub2'
        sub = 4
        self.assertEquals(sub, 4)

出力:

0.002秒で2つのテストを実行

BaseTest:testCommonの呼び出し
SubTest1:testSub1の呼び出し
BaseTest:testCommonの呼び出し
SubTest2:testSub2の呼び出し

ドキュメントから:

TestCase.setUp()
テストフィクスチャを準備するために呼び出されるメソッド。これは、テストメソッドを呼び出す直前に呼び出されます。このメソッドによって発生した例外は、テストの失敗ではなくエラーと見なされます。デフォルトの実装では何も行われません。


それはうまくいきます、もし私がtestCommonを持っているなら、それらをすべて下に置くべきsetUpですか?
ティエリーラム

1
はい、実際のテストケースではないすべてのコードをsetUpに配置する必要があります。
ブライアンR.ボンディ

ただし、サブクラスに複数のtest...メソッドがある場合、setUpそのようなメソッドごとに1回、何度も繰り返し実行されます。そこにテストを置くのは良い考えではありません!
Alex Martelli、

より複雑なシナリオで実行されたときに、OPが何を望んだのか本当にわかりません。
ブライアンR.ボンディ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.