В Python unittest.Порядок выполнения тестового случая


есть ли способ в Python unittest чтобы задать порядок выполнения тестовых случаев?

в своем TestCase класс, некоторые тестовые случаи имеют побочные эффекты, которые устанавливают условия для правильной работы других. Теперь я понимаю, что правильный способ сделать это-использовать setUp() чтобы сделать все настройки realted вещи, но я хотел бы реализовать дизайн, где каждый последующий тест строит немного больше состояния, которое может использовать следующий. Я нахожу это гораздо более элегантно.

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

в идеале, Я хотел бы, чтобы тесты выполнялись в том порядке, в котором они появляются в классе. Похоже, что они работают в алфавитном порядке.

7 59

7 ответов:

не делайте их независимыми тестами - если вы хотите монолитный тест, напишите монолитный тест.

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

  def step2(self):
      ...

  def _steps(self):
    for name in sorted(dir(self)):
      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))

Если тест позже начинает отказывать, и вы хотите получить информацию обо всех неудачных шагах вместо остановки тестового набора на первом неудачном шаге, вы можете использовать subtests особенность: https://docs.python.org/3/library/unittest.html#distinguishing-test-iterations-using-subtests

(функция субтеста доступна через unittest2 для версии до Python 3.4:https://pypi.python.org/pypi/unittest2)

его хорошая практика всегда писать монолитный тест для таких ожиданий, однако если вы тупой чувак, как я, то вы можете просто написать уродливые методы в алфавитном порядке, чтобы они были отсортированы от a до b, как указано в документах pythonhttp://docs.python.org/library/unittest.html

обратите внимание, что порядок, в котором будут выполняться различные тестовые случаи определяется путем сортировки имен тестовых функций по отношению к встроенный заказ для строк

пример:

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

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

обратите внимание, что порядок, в котором будут выполняться различные тестовые случаи, определяется сортировкой имен тестовых функций по встроенному порядку для строк.

Так что test_setup'имя S имеет наименьшее значение.

обратите внимание, что вы не должны полагаться на такое поведение - различные тестовые функции должны быть независимы от порядка о казни. посмотреть ngcohlanответ выше для решения, если вам явно нужен заказ.

старый вопрос, но другой способ, который я не видел в списке связанных вопросов:использовать TestSuite.

другой способ выполнить заказ-добавить тесты в unitest.TestSuite. Это, кажется, уважает порядок, в котором тесты добавляются в набор с помощью suite.addTest(...). Для этого:

  • создайте один или несколько подклассов 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)...')
    
  • создать вызываемый набор тестов поколения добавлено в нужном порядке, адаптированный документы и этот вопрос:

    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
    
  • выполните набор тестов, например,

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

для контекста, у меня была потребность в этом и не был удовлетворен другими вариантами. Я остановился на вышеуказанном способе выполнения заказа теста. Я не видел, чтобы этот метод TestSuite перечислял какие-либо из нескольких "вопросов упорядочения юнит-тестов" (например, это вопрос и другие, в том числе порядок выполнения или изменение порядка или тесты ордер).

тесты, которые очень зависят друг от друга должны быть явно включены в один тест.

тесты, которые требуют различных уровней настройки, также могут иметь свои соответствующие setUp() работает достаточно настройки-различные способы мыслимые.

иначе unittest ручки тестовые классы и методы испытаний внутри тестовых классов в алфавитном порядке по умолчанию (даже если loader.sortTestMethodsUsing нет). dir() используется внутри, который сортирует по гарантии.

последняя поведение может быть использовано для практичность - например, для того, чтобы сначала запустить последние рабочие тесты, чтобы ускорить цикл редактирования-testrun. Но это поведение не должно использоваться для установления реальных зависимостей. Учтите, что тесты могут выполняться индивидуально с помощью параметров командной строки и т. д.

@ncoghlan ответ был именно то, что я искал, когда я пришел к этой теме. Я закончил тем, что изменил его, чтобы позволить запускать каждый шаг-тест, даже если предыдущий шаг уже вызвал ошибку; это помогает мне (и, возможно, Вам!) для обнаружения и планирования распространения ошибок в многопоточном программном обеспечении, ориентированном на базу данных.

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)

Я закончил с простым решением, которое сработало для меня:

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

а то

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