web-dev-qa-db-ger.com

Python-Unit-Test mit Basis- und Subklasse

Ich habe derzeit ein paar Unit-Tests, die einen gemeinsamen Testsatz haben. Hier ist ein Beispiel:

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

Die Ausgabe des obigen ist:

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

OK

Gibt es eine Möglichkeit, das obige so umzuschreiben, dass die allererste testCommon nicht aufgerufen wird?

EDIT: Statt 5 Tests auszuführen, soll ich nur 4 Tests durchführen, 2 vom SubTest1 und 2 vom SubTest2. Es scheint, als würde Python allein den ursprünglichen BaseTest alleine ausführen, und ich brauche einen Mechanismus, um das zu verhindern.

113
Thierry Lam

Verwenden Sie mehrfache Vererbung, damit Ihre Klasse mit allgemeinen Tests nicht selbst von TestCase erbt.

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()
136

Verwenden Sie keine Mehrfachvererbung, es wird Sie später beißen.

Stattdessen können Sie Ihre Basisklasse einfach in das separate Modul verschieben oder mit der leeren Klasse umschließen:

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

Die Ausgabe:

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

OK
105
Vadim P.

Sie können dieses Problem mit einem einzigen Befehl lösen:

del(BaseTest)

Der Code würde also so aussehen:

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()
29
Wojciech B.

Matthew Marshalls Antwort ist großartig, aber es erfordert, dass Sie in jedem Ihrer Testfälle zwei Klassen erben, was fehleranfällig ist. Stattdessen verwende ich das (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()
20

Was willst du erreichen? Wenn Sie über allgemeinen Testcode (Assertions, Vorlagentests usw.) verfügen, platzieren Sie sie in Methoden, denen nicht test vorangestellt ist, sodass unittest sie nicht lädt.

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

Matthews Antwort ist die, die ich verwenden musste, seit ich auf 2.5 bin. Aber ab 2.7 können Sie den Dekorator @ unittest.skip () für alle Testmethoden verwenden, die Sie überspringen möchten.

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

Sie müssen einen eigenen Ausblenddekorator implementieren, um den Basistyp zu überprüfen. Ich habe diese Funktion bisher noch nicht verwendet, aber ganz oben in meinem Kopf könnten Sie BaseTest als marker -Typ verwenden, um das Überspringen zu unterdrücken:

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

Eine andere Option ist nicht auszuführen

unittest.main()

Anstelle dessen können Sie verwenden

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

Sie führen also nur die Tests in der Klasse TestClass aus.

4
Angel

Ich habe mir überlegt, wie ich dieses Problem lösen könnte, indem man die Testmethoden verbirgt, falls die Basisklasse verwendet wird. Auf diese Weise werden die Tests nicht übersprungen, sodass die Testergebnisse in vielen Testberichts-Tools grün anstatt gelb sein können.

Im Vergleich zur Mixin-Methode beklagen sich Ide's wie PyCharm nicht darüber, dass Komponententestmethoden in der Basisklasse fehlen.

Wenn eine Basisklasse von dieser Klasse erbt, müssen die Methoden setUpClass und tearDownClass überschrieben werden.

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

Ich habe ungefähr das gleiche gemacht wie @Vladim P. ( https://stackoverflow.com/a/25695512/2451329 ), aber etwas modifiziert:

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

und los geht's.

1
gst

Das ist also ein alter Faden, aber ich bin heute auf dieses Problem gestoßen und habe mir meinen eigenen Hack dafür gedacht. Es verwendet einen Dekorator, der die Werte der Funktionen auf Keine setzt, wenn über die Basisklasse zugegriffen wird. Machen Sie sich keine Gedanken über Setup und Setupclass, denn wenn die Baseclass keine Tests hat, werden sie nicht ausgeführt.

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

Benennen Sie die testCommon-Methode einfach in etwas anderes um. Unittest (normalerweise) überspringt alles, was keinen 'Test' enthält.

Schnell und einfach 

  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

Sie können __test_ = False in der BaseTest-Klasse hinzufügen. Beachten Sie jedoch, dass Sie __test__ = True in abgeleiteten Klassen hinzufügen müssen, um Tests ausführen zu können.

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()
0
peja

Ab Python 3.2 können Sie einem Modul eine test_loader - Funktion hinzufügen, um zu steuern, welche Tests (falls vorhanden) vom Testerkennungsmechanismus gefunden werden.

Im Folgenden werden beispielsweise nur die SubTest1- und SubTest2-Testfälle des ursprünglichen Posters geladen, wobei Base ignoriert wird:

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

Es sollte möglich sein, über standard_tests (eine TestSuite, die die Tests enthält, die der Standard-Loader gefunden hat) zu iterieren und stattdessen alle Variablen außer Base in suite zu kopieren, aber die verschachtelte Natur von TestSuite.__iter__ macht dies viel komplizierter.

0
jbosch