20. Убираем со стола (метод tearDown)
Вызов тестового метода
Вызов метода setUp перед обращением к методу
Вызов метода tearDown после обращения к методу
Метод tearDown должен вызываться даже в случае неудачи теста
Выполнение нескольких тестов
Отчет о результатах
Иногда для выполнения теста требуется выделить некоторые внешние ресурсы. Очевидно, что связанные с этим операции должны выполняться в теле метода setUp(). Если мы хотим, чтобы тесты были независимыми друг от друга, мы должны позаботиться об освобождении этих ресурсов. Для выполнения связанных с этим операций предлагаю использовать специальный метод tearDown(), который будет автоматически выполняться после завершения теста.
Как можно протестировать выполнение метода tearDown()? Проще всего – использовать еще один флаг. Однако все эти флаги начинают сбивать меня с толку. Если мы будем использовать флаги, мы упустим один очень важный аспект: метод setUp() должен быть выполнен непосредственно перед обращением к тестовому методу, а метод tearDown() – непосредственно после обращения к тестовому методу. Чтобы убедиться в этом, я намерен изменить стратегию тестирования. Предлагаю создать миниатюрный журнал, в котором будет отмечаться последовательность выполнения методов. Каждый метод будет добавлять в конец журнала соответствующую запись. Таким образом, просмотрев журнал, мы сможем установить порядок выполнения методов.
Вызов тестового метода
Вызов метода setUp перед обращением к методу
Вызов метода tearDown после обращения к методу
Метод tearDown должен вызываться даже в случае неудачи теста
Выполнение нескольких тестов
Отчет о результатах
Строка журнала в классе WasRun
WasRun
def setUp(self):
self.wasRun = None
self.wasSetUp = 1
self.log = "setUp "
Теперь можно изменить метод testSetUp(), чтобы вместо флага он проверял содержимое журнала:
TestCaseTest
def testSetUp(self):
self.test.run()
assert("setUp " == self.test.log)
После этого мы можем удалить флаг wasSetUp. Мы также можем добавить в журнал запись о выполнении метода:
WasRun
def testMethod(self):
self.wasRun = 1
self.log = self.log + "testMethod "
В результате нарушается работа теста testSetUp(), так как в момент выполнения этого метода журнал содержит строку «setUp testMethod». Изменяем ожидаемое значение:
TestCaseTest
def testSetUp(self):
self.test.run()
assert("setUp testMethod " == self.test.log)
Теперь этот тест выполняет работу обоих тестов, поэтому можно удалить testRunning и переименовать testSetUp:
TestCaseTest
def setUp(self):
self.test = WasRun("testMethod")
def testTemplateMethod(self):
self.test.run()
assert("setUp testMethod " == self.test.log)
Мы используем экземпляр класса WasRun всего в одном месте, поэтому необходимо отменить добавленный ранее хитрый трюк, связанный с setUp():
TestCaseTest
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run()
assert("setUp testMethod " == test.log)
Нет ничего страшного в том, что мы сделали рефакторинг, исходя из нескольких ранних соображений, а чуть позже отменили его, – подобная ситуация складывается достаточно часто. Некоторые предпочитают подождать, пока у них накопится достаточное количество оснований для рефакторинга, иными словами, они оттягивают выполнение рефакторинга, чтобы быть полностью уверенными в его необходимости. Они поступают так потому, что не любят аннулировать результаты проделанной работы. Однако я предпочитаю не отвлекаться на рассуждения о том, не придется ли в будущем отменять то или иное исправление, необходимое мне в настоящем. Вместо этого я предпочитаю сосредоточиться на дизайне. По этой причине я рефлекторно делаю рефакторинг тогда, когда считаю нужным, ни капли не опасаясь, что сразу после этого мне, возможно, придется отменить его.
Вызов тестового метода
Вызов метода setUp перед обращением к методу
Вызов метода tearDown после обращения к методу
Метод tearDown должен вызываться даже в случае неудачи теста
Выполнение нескольких тестов
Отчет о результатах
Строка журнала в классе WasRun
Теперь мы готовы к реализации метода tearDown(). Ага! Опять я вас поймал! Теперь мы готовы к тестированию метода tearDown():
TestCaseTest
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run()
assert("setUp testMethod tearDown " == test.log)
Он потерпел неудачу. Чтобы заставить его работать, выполняем несложные добавления:
TestCase
def run(self, result):
result.testStarted()
self.setUp()
exec "self." + self.name + "()"
self.tearDown()
WasRun
def setUp(self):
self.log = "setUp "
def testMethod(self):
self.log = self.log + "testMethod "
def tearDown(self):
self.log = self.log + "tearDown "
Неожиданно мы получаем ошибку не в классе WasRun, а в классе TestCaseTest. У нас нет «пустой» реализации метода teardown() в классе TestCase:
TestCase
def tearDown(self):
pass
Мы начинаем получать пользу от разрабатываемой инфраструктуры. Замечательно! Никакого рефакторинга не требуется. Очевидная реализация, созданная нами после обнаружения ошибки, сработала, и код получился чистым.
Вызов тестового метода
Вызов метода setUp перед обращением к методу
Вызов метода tearDown после обращения к методу
Метод tearDown должен вызываться даже в случае неудачи теста
Выполнение нескольких тестов
Отчет о результатах
Строка журнала в классе WasRun
Далее мы перейдем к формированию отчета о результатах выполнения тестов. Вместо использования встроенного в Python механизма обработки ошибок мы планируем реализовать и использовать собственный механизм наблюдения за работой тестов.
В данной главе мы
• перешли от использования флагов к использованию журнала;
• создали тесты для метода tearDown() и реализовали этот метод с использованием нового механизма журналирования;
• обнаружили проблему и вместо того чтобы возвращаться назад, смело исправили ошибку.