web-dev-qa-db-fra.com

Test unitaire Python avec base et sous classe

J'ai actuellement quelques tests unitaires qui partagent un ensemble commun de tests. Voici un exemple:

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

La sortie de ce qui précède est:

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

OK

Existe-t-il un moyen de réécrire ce qui précède pour que la toute première testCommon ne soit pas appelée?

EDIT: Au lieu d'exécuter 5 tests ci-dessus, je souhaite qu'il n'exécute que 4 tests, 2 du SubTest1 et 2 autres du SubTest2. Il semble que Python unittest exécute lui-même le BaseTest d'origine et j'ai besoin d'un mécanisme pour empêcher cela.

113
Thierry Lam

Utilisez plusieurs héritages pour que votre classe avec les tests courants n'hérite pas elle-même de 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()
136
Matthew Marshall

N'utilisez pas l'héritage multiple, il vous mordra plus tard .

Au lieu de cela, vous pouvez simplement déplacer votre classe de base dans le module séparé ou l'envelopper avec la classe vide:

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

Le résultat:

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

OK
105
Vadim P.

Vous pouvez résoudre ce problème avec une seule commande:

del(BaseTest)

Donc, le code ressemblerait à ceci:

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.

La réponse de Matthew Marshall est excellente, mais cela nécessite que vous héritiez de deux classes dans chacun de vos cas de test, ce qui est sujet aux erreurs. Au lieu de cela, j'utilise ceci (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
Dennis Golomazov

Qu'essayez-vous de réaliser? Si vous avez un code de test commun (assertions, tests de modèles, etc.), placez-les dans des méthodes sans préfixe test afin que unittest ne les charge pas.

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

La réponse de Matthew est celle que je devais utiliser depuis que je suis toujours sur la version 2.5 . Mais à partir de la version 2.7, vous pouvez utiliser le décorateur @ unittest.skip () sur toutes les méthodes de test que vous souhaitez ignorer.

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

Vous aurez besoin de mettre en œuvre votre propre décorateur de saut pour vérifier le type de base. Je n'ai pas utilisé cette fonctionnalité auparavant, mais vous pouvez utiliser BaseTest comme un type marker pour conditionner le saut:

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

Une autre option est de ne pas exécuter

unittest.main()

Au lieu de cela, vous pouvez utiliser

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

Donc, vous n'exécutez que les tests de la classe TestClass

4
Angel

Une façon dont j'ai pensé résoudre ce problème consiste à masquer les méthodes de test si la classe de base est utilisée. Ainsi, les tests ne sont pas ignorés. Par conséquent, les résultats des tests peuvent être verts au lieu de jaunes dans de nombreux outils de rapport de test.

Par rapport à la méthode mixin, les idées telles que PyCharm ne se plaignent pas du manque de méthodes de tests unitaires dans la classe de base.

Si une classe de base hérite de cette classe, elle devra redéfinir les méthodes setUpClass et 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

J'ai fait à peu près la même chose que @Vladim P. ( https://stackoverflow.com/a/25695512/2451329 ) mais légèrement modifié:

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

et c'est parti.

1
gst

C'est donc un peu un vieux fil, mais je suis tombé sur ce problème aujourd'hui et j'ai pensé à mon propre bidouillage. Il utilise un décorateur qui rend les valeurs des fonctions None lorsqu'il est accédé via la classe de base. Ne vous inquiétez pas pour la configuration et la classe d'installation, car si la classe de base n'a pas de test, elle ne sera pas exécutée.

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

Renommez simplement la méthode testCommon en quelque chose d'autre. Unittest (généralement) ignore tout ce qui ne contient pas de «test».

Simple et rapide 

  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

Vous pouvez ajouter __test_ = False dans la classe BaseTest, mais si vous l'ajoutez, n'oubliez pas que vous devez ajouter __test__ = True dans les classes dérivées pour pouvoir exécuter des tests.

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

A partir de Python 3.2, vous pouvez ajouter une fonction test_loader à un module pour contrôler les tests (le cas échéant) trouvés par le mécanisme de découverte de tests.

Par exemple, ce qui suit ne chargera que les cas de test SubTest1 et SubTest2 de l'affiche originale, en ignorant Base:

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

Il devrait être possible d'itérer sur standard_tests (une TestSuite contenant les tests trouvés par le chargeur par défaut) et de copier tout sauf Base dans suite à la place, mais la nature imbriquée de TestSuite.__iter__ rend cela beaucoup plus complexe.

0
jbosch