J'ai un fichier de test qui contient des tests prenant beaucoup de temps (ils envoient des calculs à un cluster et attendent le résultat). Tous ces éléments appartiennent à la classe TestCase spécifique.
Comme ils prennent du temps et qu’ils ne risquent en outre pas de casser, je voudrais pouvoir choisir si ce sous-ensemble de tests est exécuté ou non (le meilleur moyen serait d’utiliser un argument en ligne de commande, par exemple "./tests.py --offline
" ou quelque chose comme ça), donc je pouvais faire la plupart des tests souvent et rapidement et l’ensemble du jeu de temps en temps, quand j’avais le temps.
Pour l'instant, j'utilise simplement unittest.main()
pour lancer les tests.
Merci.
La unittest.main()
par défaut utilise le chargeur de test par défaut pour créer un TestSuite à partir du module dans lequel main est en cours d'exécution.
Vous n'êtes pas obligé d'utiliser ce comportement par défaut.
Vous pouvez, par exemple, créer trois unittest.TestSuite instances.
Le sous-ensemble "rapide".
fast = TestSuite()
fast.addTests( TestFastThis )
fast.addTests( TestFastThat )
Le sous-ensemble "lent".
slow = TestSuite()
slow.addTests( TestSlowAnother )
slow.addTests( TestSlowSomeMore )
L'ensemble "entier".
alltests = unittest.TestSuite([fast, slow])
Notez que j'ai ajusté les noms de TestCase pour indiquer Fast vs. Slow. Vous pouvez sous-classer Unittest.TestLoader pour analyser les noms de classes et créer plusieurs chargeurs.
Ensuite, votre programme principal peut analyser les arguments de ligne de commande avec optparse ou argparse (disponible depuis 2.7 ou 3.2) pour choisir la suite que vous souhaitez exécuter, rapide, lente ou complète.
Ou, vous pouvez avoir confiance que sys.argv[1]
est l’une des trois valeurs et utiliser quelque chose d'aussi simple que cela
if __== "__main__":
suite = eval(sys.argv[1]) # Be careful with this line!
unittest.TextTestRunner().run(suite)
Pour exécuter un seul test spécifique, vous pouvez utiliser:
$ python -m unittest test_module.TestClass.test_method
Plus d'informations ici
En fait, on peut transmettre les noms du scénario de test sous le nom sys.argv et seuls ces scénarios seront testés.
Par exemple, supposons que vous ayez
class TestAccount(unittest.TestCase):
...
class TestCustomer(unittest.TestCase):
...
class TestShipping(unittest.TestCase):
...
account = TestAccount
customer = TestCustomer
shipping = TestShipping
Tu peux appeler
python test.py account
d'avoir seulement des tests de compte, ou même
$ python test.py account customer
d'avoir les deux cas testés
Vous avez essentiellement deux façons de le faire:
Je suis un ardent défenseur de la deuxième approche. un test unitaire devrait tester uniquement une unité de code, et non des systèmes complexes (tels que des bases de données ou des clusters). Mais je comprends que ce n’est pas toujours possible; Parfois, créer des maquettes coûte tout simplement trop cher, ou le but du test est vraiment de rester dans le système complexe.
De retour à l'option (1), vous pouvez procéder de la manière suivante:
suite = unittest.TestSuite()
suite.addTest(MyUnitTestClass('quickRunningTest'))
suite.addTest(MyUnitTestClass('otherTest'))
puis en passant la suite au testeur:
unittest.TextTestRunner().run(suite)
Plus d'informations sur la documentation Python: http://docs.python.org/library/unittest.html#testsuite-objects
Je fais cela en utilisant une simple skipIf
:
import os
SLOW_TESTS = int(os.getenv('SLOW_TESTS', '0'))
@unittest.skipIf(not SLOW_TESTS, "slow")
class CheckMyFeature(unittest.TestCase):
def runTest(self):
…
De cette façon, je n'ai besoin que de décorer un scénario de test existant avec cette seule ligne (inutile de créer des suites de tests ou similaires, mais simplement cette ligne d'appel os.getenv()
au début de mon fichier de test d'unité) et, par défaut, ce test est ignoré.
Si je veux l'exécuter malgré ma lenteur, j'appelle simplement mon script comme suit:
SLOW_TESTS=1 python -m unittest …
Puisque vous utilisez unittest.main()
, vous pouvez simplement exécuter python tests.py --help
pour obtenir la documentation:
Usage: tests.py [options] [test] [...]
Options:
-h, --help Show this message
-v, --verbose Verbose output
-q, --quiet Minimal output
-f, --failfast Stop on first failure
-c, --catch Catch control-C and display results
-b, --buffer Buffer stdout and stderr during test runs
Examples:
tests.py - run default set of tests
tests.py MyTestSuite - run suite 'MyTestSuite'
tests.py MyTestCase.testSomething - run MyTestCase.testSomething
tests.py MyTestCase - run all 'test*' test methods
in MyTestCase
C'est, vous pouvez simplement faire
python tests.py TestClass.test_method
J'ai trouvé une autre solution, basée sur le fonctionnement du décorateur unittest.skip
. En définissant __unittest_skip__
et __unittest_skip_why__
.
Basé sur les étiquettes
Je voulais appliquer un système d'étiquetage, étiqueter des tests comme suit: quick
, slow
, glacier
, memoryhog
, cpuhog
, core
, etc.
Ensuite, lancez all 'quick' tests
ou run everything except 'memoryhog' tests
, votre configuration de base de liste blanche/liste noire
La mise en oeuvre
J'ai implémenté ceci en 2 parties:
@testlabel
personnalisé)unittest.TestRunner
pour identifier les tests à ignorer et modifier le contenu de la liste de tests avant exécution.L'implémentation de travail est dans cet Gist: https://Gist.github.com/fragmuffin/a245f59bdcd457936c3b51aa2ebb3f6c
(un exemple pleinement fonctionnel était trop long à mettre ici)
Le résultat étant ...
$ ./runtests.py --blacklist foo
test_foo (test_things.MyTest2) ... ok
test_bar (test_things.MyTest3) ... ok
test_one (test_things.MyTests1) ... skipped 'label exclusion'
test_two (test_things.MyTests1) ... skipped 'label exclusion'
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK (skipped=2)
Tous les tests de la classe MyTests1
sont ignorés car ils portent l'étiquette foo
.
--whitelist
fonctionne aussi
Ou vous pouvez utiliser la fonction unittest.SkipTest()
. Exemple, ajoutez une méthode skipOrRunTest
à votre classe de test comme ceci:
def skipOrRunTest(self,testType):
#testsToRun = 'ALL'
#testsToRun = 'testType1, testType2, testType3, testType4,...etc'
#testsToRun = 'testType1'
#testsToRun = 'testType2'
#testsToRun = 'testType3'
testsToRun = 'testType4'
if ((testsToRun == 'ALL') or (testType in testsToRun)):
return True
else:
print "SKIPPED TEST because:\n\t testSuite '" + testType + "' NOT IN testsToRun['" + testsToRun + "']"
self.skipTest("skipppy!!!")
Ajoutez ensuite un appel à cette méthode skipOrRunTest au tout début de chacun de vos tests unitaires, comme suit:
def testType4(self):
self.skipOrRunTest('testType4')
J'ai essayé la réponse de @ slott:
if __== "__main__":
suite = eval(sys.argv[1]) # Be careful with this line!
unittest.TextTestRunner().run(suite)
Mais cela m'a donné l'erreur suivante:
Traceback (most recent call last):
File "functional_tests.py", line 178, in <module>
unittest.TextTestRunner().run(suite)
File "/usr/lib/python2.7/unittest/runner.py", line 151, in run
test(result)
File "/usr/lib/python2.7/unittest/case.py", line 188, in __init__
testMethod = getattr(self, methodName)
TypeError: getattr(): attribute name must be string
Ce qui suit a fonctionné pour moi:
if __== "__main__":
test_class = eval(sys.argv[1])
suite = unittest.TestLoader().loadTestsFromTestCase(test_class)
unittest.TextTestRunner().run(suite)
Envisagez d'utiliser un testrunner dédié, tel que py.test, nose ou éventuellement zope.testing. Ils ont tous des options de ligne de commande pour la sélection des tests.
Recherchez par exemple Nose: https://pypi.python.org/pypi/nose/1.3.0
J'ai créé un décorateur qui permet de marquer les tests comme des tests lents et de les ignorer à l'aide d'une variable d'environnement.
from unittest import skip
import os
def slow_test(func):
return skipIf('SKIP_SLOW_TESTS' in os.environ, 'Skipping slow test')(func)
Maintenant, vous pouvez marquer vos tests aussi lentement que ceci:
@slow_test
def test_my_funky_thing():
perform_test()
Et ignorez les tests lents en définissant la variable d’environnement SKIP_SLOW_TESTS
:
SKIP_SLOW_TESTS=1 python -m unittest
N'ayant pas trouvé de moyen agréable de le faire auparavant, partagez ici.
Objectif: réunir un ensemble de fichiers de test afin de pouvoir les exécuter en tant qu’unité, Mais nous pouvons toujours sélectionner l’un d’eux à exécuter par lui-même.
Problème: la méthode de découverte ne permet pas de sélectionner facilement un seul cas de test à exécuter.
Design: voir ci-dessous. Ceci aplatit l’espace de noms peut donc être sélectionné par nom de classe TestCase et ne pas utiliser le préfixe "tests1.test_core":
./run-tests TestCore.test_fmap
Code
test_module_names = [
'tests1.test_core',
'tests2.test_other',
'tests3.test_foo',
]
loader = unittest.defaultTestLoader
if args:
alltests = unittest.TestSuite()
for a in args:
for m in test_module_names:
try:
alltests.addTest( loader.loadTestsFromName( m+'.'+a ) )
except AttributeError as e:
continue
else:
alltests = loader.loadTestsFromNames( test_module_names )
runner = unittest.TextTestRunner( verbosity = opt.verbose )
runner.run( alltests )
C'est la seule chose qui a fonctionné pour moi.
if __== '__main__':
unittest.main( argv=sys.argv, testRunner = unittest.TextTestRunner(verbosity=2))
Lorsque je l’ai appelé, j’ai dû donner le nom de la classe et le nom du test. Un peu gênant puisque je n'ai pas de combinaison de nom de classe et de test mémorisée.
python ./tests.py nom_classe.test_30311
La suppression du nom de la classe et du nom du test exécute tous les tests de votre fichier. Je trouve BEAUCOUP plus facile à gérer que la méthode intégrée car je ne change pas vraiment ma commande sur la CLI. Il suffit d'ajouter le paramètre.
Profitez, Keith
J'ai trouvé un autre moyen de sélectionner les méthodes test_ * que je souhaite exécuter uniquement en leur attribuant un attribut. Vous utilisez essentiellement une métaclasse pour décorer les appelables de la classe TestCase ayant l'attribut StepDebug avec un décorateur unittest.skip. Plus d'infos sur
Ignorer tous les tests unitaires sauf un en Python en utilisant des décorateurs et des métaclasses
Je ne sais pas si c'est une meilleure solution que celles ci-dessus, je la propose simplement comme option.