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.
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()
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
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()
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()
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()
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
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
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 = []
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.
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()
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()`
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()
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.