web-dev-qa-db-ja.com

Python基本クラスとサブクラスを使用した単体テスト

現在、共通のテストセットを共有するいくつかのユニットテストがあります。以下に例を示します。

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 __== '__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つのテストを実行する代わりに、SubTest1から2つ、SubTest2から2つのテストを4つだけ実行するようにします。 Python unittestは独自に元のBaseTestを実行しているため、それを防ぐメカニズムが必要です。

128
Thierry Lam

多重継承を使用して、共通のテストを含むクラスが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 __== '__main__':
    unittest.main()
144

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

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

import unittest

class BaseTestCases:

    class BaseTest(unittest.TestCase):

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


class SubTest1(BaseTestCases.BaseTest):

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


class SubTest2(BaseTestCases.BaseTest):

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

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

出力:

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

OK
116
Vadim P.

単一のコマンドでこの問題を解決できます。

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 __== '__main__':
    unittest.main()
30
Wojciech B.

Matthew Marshallの答えは素晴らしいですが、テストケースごとに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()
21

何を達成しようとしていますか?共通のテストコード(アサーション、テンプレートテストなど)がある場合は、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 __== '__main__':
    unittest.main()
8
John Millikin

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

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

基本型を確認するには、独自のスキップデコレータを実装する必要があります。これまでこの機能を使用したことはありませんが、頭の中で、BaseTestをmarkerタイプとして使用して、スキップを調整できます。

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

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

unittest.main()

その代わりに使用できます

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

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

4
Angel

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

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 = []
4
simonzack

BaseTestクラスに__test_ = Falseを追加できますが、追加する場合は、テストを実行できるように派生クラスに__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 __== '__main__':
    unittest.main()
2
peja

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

これは一種の古いスレッドですが、今日この問題に遭遇し、それに対する私自身のハックを考えました。基本クラスを介してアクセスされると、関数の値を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 __== '__main__':
    unittest.main()
0
Nathan Buckner

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 __== '__main__':
      unittest.main()`
0
Kashif Siddiqui

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

たとえば、次のコードは元のポスターのSubTest1およびSubTest2テストケースのみを読み込み、Baseを無視します。

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

standard_tests(デフォルトのローダーが検出したテストを含むTestSuite)を反復処理し、代わりにBaseを除くすべてをsuiteにコピーする必要がありますが、 TestSuite.__iter__のネストされた性質により、それはさらに複雑になります。

0
jbosch