web-dev-qa-db-fra.com

Python unittest.TestCase ordre d'exécution

Existe-t-il un moyen dans Python unittest pour définir l'ordre dans lequel les cas de test sont exécutés?

Dans ma classe TestCase actuelle, certains tests ont des effets secondaires qui définissent les conditions pour que les autres fonctionnent correctement. Maintenant, je me rends compte que la bonne façon de le faire est d'utiliser setUp() pour faire toutes les choses de configuration, mais je voudrais implémenter une conception où chaque test successif crée un état légèrement supérieur à celui que le suivant peut utiliser. Je trouve cela beaucoup plus élégant.

class MyTest(TestCase):
  def test_setup(self):
   #do something
  def test_thing(self)
   #do something that depends on test_setup()

Idéalement, j'aimerais que les tests soient exécutés dans l'ordre où ils apparaissent dans la classe. Il semble qu'ils fonctionnent par ordre alphabétique.

65
Mike

Ne faites pas d'eux des tests indépendants - si vous voulez un test monolithique, écrivez un test monolithique.

class Monolithic(TestCase):
  def step1(self):
      ...

  def step2(self):
      ...

  def _steps(self):
    for name in dir(self): # dir() result is implicitly sorted
      if name.startswith("step"):
        yield name, getattr(self, name) 

  def test_steps(self):
    for name, step in self._steps():
      try:
        step()
      except Exception as e:
        self.fail("{} failed ({}: {})".format(step, type(e), e))

Si le test échoue plus tard et que vous souhaitez obtenir des informations sur toutes les étapes ayant échoué au lieu d'arrêter le scénario de test à la première étape ayant échoué, vous pouvez utiliser la fonction subtests: https: //docs.python. org/3/library/unittest.html # distinction-test-itérations-utilisant-sous-tests

(La fonction de sous-test est disponible via unittest2 pour les versions antérieures à Python 3.4: https://pypi.python.org/pypi/unittest2 )

66
ncoghlan

C'est une bonne pratique de toujours écrire un test monolithique pour de telles attentes, mais si vous êtes un mec maladroit comme moi, alors vous pouvez simplement écrire des méthodes laides dans un ordre alphabétique afin qu'elles soient triées de a à b comme mentionné dans le python docs http://docs.python.org/library/unittest.html

Notez que l'ordre dans lequel les divers cas de test seront exécutés est déterminé en triant les noms des fonctions de test par rapport à l'ordre intégré des chaînes

EXEMPLE:

  def test_a_first():
  print "1"
  def test_b_next(): 
  print "2" 
  def test_c_last(): 
  print "3"
32
varun

http://docs.python.org/library/unittest.html

Notez que l'ordre dans lequel les divers cas de test seront exécutés est déterminé en triant les noms des fonctions de test par rapport à l'ordre intégré des chaînes.

Assurez-vous donc que le nom de test_setup A la plus petite valeur de chaîne.

Notez que vous ne devez pas vous fier à ce comportement - différentes fonctions de test sont censées être indépendantes de l'ordre d'exécution. Voir ngcohlan = réponse ci-dessus pour une solution si vous avez explicitement besoin d'une commande.

23
kennytm

Ancienne question, mais d'une autre manière que je n'ai pas vue dans les questions connexes: Utilisez un TestSuite.

Une autre façon de réaliser la commande consiste à ajouter les tests à un unitest.TestSuite . Cela semble respecter l'ordre dans lequel les tests sont ajoutés à la suite à l'aide de suite.addTest(...). Pour faire ça:

  • Créez une ou plusieurs sous-classes TestCase,

    class FooTestCase(unittest.TestCase):
        def test_ten():
            print('Testing ten (10)...')
        def test_eleven():
            print('Testing eleven (11)...')
    
    class BarTestCase(unittest.TestCase):
        def test_twelve():
            print('Testing twelve (12)...')
        def test_nine():
            print('Testing nine (09)...')
    
  • Créez une génération de suite de tests appelable ajouté dans votre ordre, adapté de les docs = et cette question :

    def suite():
        suite = unittest.TestSuite()
        suite.addTest(BarTestCase('test_nine'))
        suite.addTest(FooTestCase('test_ten'))
        suite.addTest(FooTestCase('test_eleven'))
        suite.addTest(BarTestCase('test_twelve'))
        return suite
    
  • Exécutez la suite de tests, par exemple,

    if __== '__main__':
        runner = unittest.TextTestRunner(failfast=True)
        runner.run(suite())
    

Pour le contexte, j'avais besoin de cela et je n'étais pas satisfait des autres options. J'ai choisi la méthode ci-dessus pour passer une commande de test. Je n'ai pas vu cette méthode TestSuite répertoriée parmi les plusieurs "questions d'ordre de test unitaire" (par exemple, cette question et d'autres, y compris ordre d'exécution , ou ordre changeant , ou ordre des tests ).

15
hoc_age

Je me suis retrouvé avec une solution simple qui a fonctionné pour moi:

class SequentialTestLoader(unittest.TestLoader):
    def getTestCaseNames(self, testCaseClass):
        test_names = super().getTestCaseNames(testCaseClass)
        testcase_methods = list(testCaseClass.__dict__.keys())
        test_names.sort(key=testcase_methods.index)
        return test_names

Et alors

unittest.main(testLoader=utils.SequentialTestLoader())
3
caio

Les tests qui dépendent vraiment les uns des autres doivent être explicitement enchaînés en un seul test.

Les tests qui nécessitent différents niveaux de configuration, pourraient également avoir leur setUp() correspondante exécutant suffisamment de configuration - de différentes manières envisageables.

Sinon, unittest gère les classes de test et les méthodes de test à l'intérieur des classes de test par ordre alphabétique par défaut (même lorsque loader.sortTestMethodsUsing Vaut None). dir() est utilisé en interne qui trie par garantie.

Ce dernier comportement peut être exploité pour praticabilité - par ex. pour que les derniers tests de travail s'exécutent en premier pour accélérer le cycle d'édition-test. Mais ce comportement ne doit pas être utilisé pour établir dépendances réelles. Considérez que les tests peuvent être exécutés individuellement via les options de ligne de commande, etc.

1
kxr

La réponse de @ ncoghlan était exactement ce que je cherchais lorsque je suis arrivée sur ce sujet. J'ai fini par le modifier pour permettre à chaque test d'étape de s'exécuter, même si une étape précédente avait déjà généré une erreur; cela m'aide (et peut-être vous!) à découvrir et à planifier la propagation des erreurs dans les logiciels multi-threads centrés sur les bases de données.

class Monolithic(TestCase):
  def step1_testName1(self):
      ...

  def step2_testName2(self):
      ...

  def steps(self):
      '''
      Generates the step methods from their parent object
      '''
      for name in sorted(dir(self)):
          if name.startswith('step'):
              yield name, getattr(self, name)

  def test_steps(self):
      '''
      Run the individual steps associated with this test
      '''
      # Create a flag that determines whether to raise an error at
      # the end of the test
      failed = False

      # An empty string that the will accumulate error messages for 
      # each failing step
      fail_message = ''
      for name, step in self.steps():
          try:
              step()
          except Exception as e:
              # A step has failed, the test should continue through
              # the remaining steps, but eventually fail
              failed = True

              # get the name of the method -- so the fail message is
              # nicer to read :)
              name = name.split('_')[1]
              # append this step's exception to the fail message
              fail_message += "\n\nFAIL: {}\n {} failed ({}: {})".format(name,
                                                                       step,
                                                                       type(e),
                                                                       e)

      # check if any of the steps failed
      if failed is True:
          # fail the test with the accumulated exception message
          self.fail(fail_message)
0
dslosky