Python unittest.TestCase порядок выполнения - PullRequest
64 голосов
/ 22 марта 2011

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

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

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

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

Ответы [ 7 ]

66 голосов
/ 22 марта 2011

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

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

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

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

31 голосов
/ 17 августа 2011

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

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

Пример:

  def test_a_first():
  print "1"
  def test_b_next(): 
  print "2" 
  def test_c_last(): 
  print "3"
21 голосов
/ 22 марта 2011

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

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

Так что просто убедитесь, что имя test_setup имеет наименьшее строковое значение.

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

13 голосов
/ 05 апреля 2016

Старый вопрос, но другой способ, который я не видел в списке связанных вопросов: Используйте 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 перечислял ни один из нескольких «вопросов о порядке юнит-тестов» (например, этот вопрос и другие, включая порядок выполнения или изменение порядка или тестовый заказ ).

3 голосов
/ 11 мая 2018

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

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())
1 голос
/ 02 марта 2017

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

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

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

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

0 голосов
/ 03 июня 2016

@ Ответ 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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...