def testTemplateMethod(self):
test = WasRun("testMethod")
result = TestResult()
test.run(result)
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test = WasRun("testMethod")
result = TestResult()
test.run(result)
assert("1 run, 0 failed" == result.summary())
def testFailedResult(self):
test = WasRun("testBrokenMethod")
result = TestResult()
test.run(result)
assert("1 run, 1 failed" == result.summary())
def testFailedResultFormatting(self):
result = TestResult()
result.testStarted()
result.testFailed()
assert("1 run, 1 failed" == result.summary())
Обратите внимание, что каждый из тестов создает экземпляр класса TestResult – эту операцию можно выполнить однократно внутри метода setUp(). Благодаря реализации этой идеи мы упростим тесты, однако сделаем их несколько более сложными в прочтении:
TestCaseTest
def setUp(self):
self.result = TestResult()
def testTemplateMethod(self):
test = WasRun("testMethod")
test.run(self.result)
assert("setUp testMethod tearDown " == test.log)
def testResult(self):
test = WasRun("testMethod")
test.run(self.result)
assert("1 run, 0 failed" == self.result.summary())
def testFailedResult(self):
test = WasRun("testBrokenMethod")
test.run(self.result)
assert("1 run, 1 failed" == self.result.summary())
def testFailedResultFormatting(self):
self.result.testStarted()
self.result.testFailed()
assert("1 run, 1 failed" == self.result.summary())
def testSuite(self):
suite = TestSuite()
suite.add(WasRun("testMethod"))
suite.add(WasRun("testBrokenMethod"))
suite.run(self.result)
assert("2 run, 1 failed" == self.result.summary())
Вызов тестового метода
Вызов метода setUp перед обращением к методу
Вызов метода tearDown после обращения к методу
Метод tearDown должен вызываться даже в случае неудачи теста
Выполнение нескольких тестов
Отчет о результатах
Строка журнала в классе WasRun
Отчет о неудачных тестах
Перехват и отчет об ошибках setUp
Создать объект TestSuite автоматически на основе класса TestCase
Все эти бесчисленные ссылки self выглядят ужасно, однако без этого в языке Python никак не обойтись. Если бы этот язык изначально был объектно-ориентированным, наверное, в этих ссылках не было бы надобности, а ссылки на глобальные переменные требовали бы квалификации. Однако язык Python изначально является интерпретируемым языком с добавленной в него поддержкой объектов (надо отметить, что поддержка объектов в этом языке реализована великолепно). В результате по умолчанию переменные считаются глобальными, а явные ссылки на self – необходимыми.
Я оставляю реализацию оставшихся пунктов вам в качестве упражнения. Надеюсь, обретенные навыки работы в стиле TDD помогут вам.
Чтобы подвести итог, напомню, что в данной главе мы
• написали тест для класса TestSuite;
• написали часть реализации, однако не добились успешного выполнения тестов – это нарушение правил (я уверен, что существует простая поддельная реализация, которая заставила бы тесты работать, благодаря чему мы могли бы выполнять рефакторинг, имея перед глазами зеленую полоску, однако сейчас я не хочу думать на эту тему);
• изменили интерфейс метода run(), благодаря чему набор тестов можно использовать точно так же, как и отдельный тест, – в результате тесты наконец выполнились успешно;
• выполнили рефакторинг имеющихся тестов – переместили общий код создания объекта результатов в метод setUp().
24. Ретроспектива xUnit
Если перед вами встала задача разработки своей собственной инфраструктуры тестирования, методика, описанная в части II данной книги, послужит вам руководством. Не следует слишком много внимания уделять деталям реализации – значительно больший интерес представляют тесты. Если вы напишете код, обеспечивающий успешное выполнение представленных здесь тестов, в вашем распоряжении окажется минимальная инфраструктура тестирования, пригодная для запуска тестов в условиях изоляции и обеспечивающая композицию тестов. Вы сможете приступить к разработке программного кода в стиле TDD.
На момент написания данной книги инфраструктура тестирования xUnit адаптирована для более чем 30 языков программирования. Язык, на котором вы программируете, скорее всего, уже обладает своей собственной реализацией xUnit. Однако, даже если кто-то уже сделал это до вас, возможно, будет лучше, если вы попробуете разработать свою собственную новую версию xUnit самостоятельно. На то есть две важные причины:
Контроль над реализацией. Основополагающая характеристика xUnit – это простота. Мартин Фаулер (Martin Fowler) сказал: «Никогда в истории программной индустрии еще не было случая, чтобы столь многие разработчики были обязаны столь немногому количеству строк программного кода». На мой взгляд, некоторые реализации xUnit к настоящему времени стали слишком большими и сложными. Если вы разработаете собственную версию xUnit, то получите инструмент, который вы будете контролировать в полном объеме.
Обучение. Когда я сталкиваюсь с необходимостью изучить новый язык программирования, я приступаю к реализации xUnit. Когда я добиваюсь срабатывания первых восьми-десяти тестов, я овладеваю навыками работы с основными конструкциями и возможностями нового для меня языка.
Когда вы начнете работать с xUnit, вы обнаружите, что существует значительная разница между выражениями assert, потерпевшими неудачу, и ошибками других типов, возникающими в процессе выполнения тестов. В отличие от остальных ошибок выражения assert требуют больше времени для отладки. Из-за этого большинство реализаций xUnit отличает сбои операторов assert от всех остальных ошибок: в рамках GUI зачастую информация об ошибках отображается в начале списка.
Инфраструктура JUnit объявляет простой интерфейс Test, который реализуется классами TestCase и TestSuite. Если вы хотите создать тестовый класс, который мог бы взаимодействовать со стандартными средствами тестирования, встроенными в JUnit, вы можете реализовать функции интерфейса Test самостоятельно:
public interface Test {
public abstract int countTestCases();
public abstract void run(TestResult result);
}
В языках с оптимистическим (динамическим) приведением типов можно даже не объявлять о поддержке этого интерфейса – достаточно реализовать входящие в его состав операции. При использовании языка сценариев Сценарий может ограничивать реализацию countTestCases() возвратом единицы и выполнять проверку TestResult на отказ, а вы можете выполнять ваши сценарии с обычными объектами TestCase.
Часть III. Шаблоны разработки через тестирование
Далее следуют «величайшие хиты» – шаблоны разработки через тестирование. Некоторые из них являются эффективными приемами работы в стиле TDD, другие – шаблонами проектирования и, наконец, третьи – шаблонами рефакторинга. Третья часть книги является коллекцией справочного материала, необходимого как для освоения представленных в книге примеров, так и для самостоятельного совершенствования навыков работы в стиле TDD. Здесь представлены сведения, которые помогут лучше понять смысл примеров, рассмотренных в первых двух частях книги, а также подогреют ваш интерес и стимулируют обратиться к дополнительной информации, которую следует искать в других источниках.
25. Шаблоны