Как правильно сообщить об ошибке в тесте Python в методе setUp?
Я прочитал несколько противоречивых советов по использованию assert
в setUp
метод модульного теста Python. Я не вижу вреда в провале теста, если не выполнено предварительное условие, на которое опирается тест.
Например:
import unittest
class MyProcessor():
"""
This is the class under test
"""
def __init__(self):
pass
def ProcessData(self, content):
return ['some','processed','data','from','content'] # Imagine this could actually pass
class Test_test2(unittest.TestCase):
def LoadContentFromTestFile(self):
return None # Imagine this is actually doing something that could pass.
def setUp(self):
self.content = self.LoadContentFromTestFile()
self.assertIsNotNone(self.content, "Failed to load test data")
self.processor = MyProcessor()
def test_ProcessData(self):
results = self.processor.ProcessData(self.content)
self.assertGreater(results, 0, "No results returned")
if __name__ == '__main__':
unittest.main()
Мне кажется, что это разумная вещь, т.е. убедиться, что тест может быть запущен. Когда это не удается из-за условия установки, мы получаем:
F
======================================================================
FAIL: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Projects\Experiments\test2.py", line 21, in setUp
self.assertIsNotNone(self.content, "Failed to load test data")
AssertionError: unexpectedly None : Failed to load test data
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (failures=1)
5 ответов
Цель setUp
заключается в сокращении кода Boilerplate, который создается между тестами в классе теста на этапе Arrange.
На этапе Arrange вы: настраиваете все необходимое для запуска тестируемого кода. Это включает в себя любую инициализацию зависимостей, макетов и данных, необходимых для запуска теста.
Исходя из приведенных выше пунктов, вы не должны утверждать что-либо в вашем setUp
метод.
Так как упоминалось ранее; Если вы не можете создать предварительное условие теста, значит, ваш тест не пройден. Чтобы избежать подобных ситуаций, Рой Ошеров написал большую книгу под названием "Искусство модульного тестирования" (для полного раскрытия информации Лиор Фридман (он был боссом Роя)) - мой друг, и я работал с ними более двух лет, поэтому я немного предвзятый...)
По существу, существует всего несколько причин для взаимодействия с внешними ресурсами на этапе Arrange (или с вещами, которые могут вызвать исключение), большинство из которых (если не все) связаны в интеграционных тестах.
Вернемся к вашему примеру; Существует шаблон для структурирования тестов, в которых необходимо загрузить внешний ресурс (для всех / большинства из них). Просто примечание стороны; прежде чем вы решите применить этот шаблон, убедитесь, что у вас нет этого содержимого в качестве статического ресурса в классе UT, если другие тестовые классы должны использовать этот ресурс, извлеките этот ресурс в модуль.
Следующая схема уменьшает вероятность сбоя, так как у вас меньше обращений к внешнему ресурсу:
class TestClass(unittest.TestCase):
def setUpClass(self):
# since external resources such as other servers can provide a bad content
# you can verify that the content is valid
# then prevent from the tests to run
# however, in most cases you shouldn't.
self.externalResourceContent = loadContentFromExternalResource()
def setUp(self):
self.content = self.copyContentForTest()
Плюсы:
- меньше шансов на провал
- предотвратить несогласованность действий (1. что-то / один отредактировал внешний ресурс. 2. вам не удалось загрузить внешний ресурс в некоторых ваших тестах)
- более быстрое исполнение
Минусы:
- код более сложный
setUp
не для утверждения предварительных условий, но для их создания. Если ваш тест не может создать необходимый прибор, он не работает.
Из документации стандартной библиотеки Python:
"Если метод setUp() вызывает исключение во время выполнения теста, среда будет считать, что в тесте произошла ошибка, и метод runTest() не будет выполнен. Если setUp() завершился успешно, метод tearDown() будет запущен независимо от того, успешно ли выполнена runTest(). Такая рабочая среда для кода тестирования называется фикстурой."
Исключение утверждения в методе setUp() будет считаться ошибкой в среде unittest. Тест не будет выполнен.
Есть одна причина, почему вы хотите избежать утверждений в setUp()
, Если setUp не удается, ваш tearDown не будет выполнен.
Если вы, например, настроили набор записей базы данных, и ваше удаление удаляет эти записи, то эти записи не будут удалены.
С этим фрагментом:
import unittest
class Test_test2(unittest.TestCase):
def setUp(self):
print 'setup'
assert False
def test_ProcessData(self):
print 'testing'
def tearDown(self):
print 'teardown'
if __name__ == '__main__':
unittest.main()
Вы запускаете только setUp()
:
$ python t.py
setup
E
======================================================================
ERROR: test_ProcessData (__main__.Test_test2)
----------------------------------------------------------------------
Traceback (most recent call last):
File "t.py", line 7, in setUp
assert False
AssertionError
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
Здесь нет правильного или неправильного ответа, это зависит от того, что вы тестируете, и того, сколько стоит установка ваших тестов. Некоторые тесты слишком опасны, чтобы разрешать попытки запуска, если данные не соответствуют ожидаемым, а некоторые должны работать с этими данными.
Вы можете использовать утверждения в setUp, если вам нужно проверять между тестами определенные условия, это может помочь уменьшить количество повторяющихся кодов в ваших тестах. Однако это также усложняет перемещение методов тестирования между классами или файлами, так как они будут зависеть от наличия эквивалентного setUp. Это также может расширить границы сложности для менее опытных тестировщиков кода.
Немного чище иметь тест, который проверяет эти условия запуска по отдельности и запускает его сначала, они могут не понадобиться между каждым тестом. Если вы определите его как test_01_check_preconditions, это будет сделано перед любым из других методов тестирования, даже если остальные являются случайными. Вы также можете использовать декораторы unittest2.skip для определенных условий.
Лучшим подходом является использование addCleanup для обеспечения сброса состояния. Преимущество здесь в том, что даже если тест не пройден, он все равно запускается, вы также можете сделать очистку более осведомленной о конкретной ситуации, определяя ее в контексте вашей ситуации. Метод испытания.
Также ничто не мешает вам определять методы для выполнения общих проверок в классе unittest и вызывать их в setUp или test_methods, это может помочь сохранить сложность включенной в определенные и управляемые области.
Также не поддавайтесь соблазну подкласса unittest2 за пределами простого определения теста, я видел, как люди пытаются сделать это, чтобы сделать тесты простыми и фактически ввести совершенно неожиданное поведение.
Я полагаю, что реальный смысл в том, что если вы делаете это, знаете, почему вы хотите его использовать, и гарантируете, что вы документируете свои причины, то это, вероятно, нормально, если вы не уверены, тогда выберите самый простой и понятный вариант, потому что тесты бесполезны, если они не легко понять.