Порядок выполнения Python unittest.TestCase
Есть ли способ в Python unittest
установить порядок выполнения тестовых примеров?
В моем текущем классе TestCase
некоторые тестовые окна имеют побочные эффекты, которые задают условия для правильной работы остальных. Теперь я понимаю, что правильный способ сделать это состоит в том, чтобы использовать setUp()
для выполнения всех заданий, связанных с реализацией, но я хотел бы реализовать проект, в котором каждый последующий тест строит немного больше состояний, которые может использовать следующий. Я нахожу это намного более элегантным.
class MyTest(TestCase):
def test_setup(self):
#do something
def test_thing(self)
#do something that depends on test_setup()
В идеале я бы хотел, чтобы тесты выполнялись в том порядке, в котором они появляются в классе. Похоже, что они работают в алфавитном порядке.
Ответы
Ответ 1
Не делайте их независимыми испытаниями - если вы хотите монолитный тест, напишите монолитный тест.
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 # отличительных-тест-итерации, использующие-подтесты
(Функция unittest2
доступна через unittest2
для версий до Python 3.4: https://pypi.python.org/pypi/unittest2)
Ответ 2
Хорошая практика всегда писать монолитный тест для таких ожиданий, однако, если у вас тупой чувак, как я, то вы можете просто писать уродливые поисковые методы в алфавитном порядке, чтобы они сортировались от a до b, как указано в python docs 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"
Ответ 3
http://docs.python.org/library/unittest.html
Обратите внимание, что порядок, в котором будут выполняться различные тестовые примеры, определяется путем сортировки имен тестовых функций по отношению к встроенному упорядочению для строк.
Поэтому просто убедитесь, что имя test_setup
имеет наименьшее строковое значение.
Обратите внимание, что вы не должны полагаться на это поведение - разные функции тестирования должны быть независимы от порядка выполнения. См. ngcohlan ответьте выше для решения, если вам явно нужен заказ.
Ответ 4
Старый вопрос, но другой способ, который я не видел, указан в любых связанных вопросах: используйте 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 перечислял любой из нескольких "вопросов упорядочивания модульных тестов" (например, этот вопрос и другие, включая порядок выполнения или изменение порядка или порядок тестирования).
Ответ 5
Тесты, которые действительно зависят друг от друга, должны быть явно привязаны к одному тесту.
Тесты, требующие разных уровней настройки, также могут иметь соответствующий setUp()
режим работы, достаточный для установки - различные способы мыслимы.
В противном случае unittest
обрабатывает тестовые классы и методы тестирования внутри классов тестов по алфавиту по умолчанию (даже если loader.sortTestMethodsUsing
- None). dir()
используется внутри, который сортируется по гарантии.
Последнее поведение может быть использовано для практичности - например. для того, чтобы выполнить самые последние рабочие тесты, чтобы ускорить цикл edit-testrun.
Но это поведение не должно использоваться для установления реальных зависимостей. Учтите, что тесты можно запускать индивидуально с помощью параметров командной строки и т.д.
Ответ 6
Ответ на @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 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)
Ответ 7
Я закончил с простым решением, которое сработало для меня:
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())