Как стать автором
Обновить

Комментарии 22

Спасибо за статью, но мне опять кажется, что пример не совсем удачный.
Результат достигнут — мы убедились, что внутри класса всё работает как и должно.
Принято считать, что тестировать внутренности класса плохо: нарушается инкапсуляция, появляется зависимость от реализации и бедный программист, который позже будет вносить изменения или пытаться понять, как все работает.
Я не призываю вас тестировать закрытые члены класса. Но если классу предоставляется возможность наследования, как в приведенном варианте, то тестирование его protected части вполне себе оправдано. А насчет внесения изменений и понять как все работает — в этом как-раз написанные тесты вам и помогут.
Вот как только класс будет отнаследован — тогда и будете тестировать наследника.

А пока — вы предложили создать хрупкие тесты, завязанные на реализацию. Которые сломаются от малейшего рефакторинга.
Вот как только класс будет отнаследован — тогда и будете тестировать наследника.

Вы меня не поняли, я вообще не собирался тестировать наследника. Я тестировал работу исходного класса, к тому же его Public-метода
А пока — вы предложили создать хрупкие тесты, завязанные на реализацию. Которые сломаются от малейшего рефакторинга.

В конце статьи я, кстати, указал на этот недостаток. В ряде задач, реализующих последовательность некоторых шагов как, например, Шаблонный метод правильность алгоритма можно протестировать именно через тестовый класс-наследник, как показано в примере. Если вы предложите лучший вариант тестирования, без завязки на реализацию, то с удовольствием выслушаю вас…

Подходы могут быть различные, ничего в этом плохого нет — все зависит от конкретной задачи. Что б лучше понять — немного процитирую (кстати мой вариант второй).
"… для тестирования классов применяются тестовые драйверы. Существует несколько способов реализации тестового драйвера:

Тестовый драйвер реализуется в виде отдельного класса. Методы этого класса создают объекты тестируемого класса и вызывают их методы, в том числе статические методы класса. Таким способом можно тестировать public часть класса.

Тестовый драйвер реализуется в виде класса, наследуемого от тестируемого. В отличие от предыдущего способа, такому тестовому драйверу доступна не только public, но и protected часть.

Тестовый драйвер реализуется непосредственно внутри тестируемого класса (в класс добавляются диагностические методы). Такой тестовый драйвер имеет доступ ко всей реализации класса, включая private члены. В этом случае в методы класса включаются вызовы отладочных функций и агенты, отслеживающие некоторые события при тестировании."

«Если вы предложите лучший вариант тестирования, без завязки на реализацию, то с удовольствием выслушаю вас…»

Лучший вариант тестирования — тестировать поведение, а не реализацию. Вне зависимости от реализованных паттернов проектирования.

Как только у вас появится класс, который будет реализовывать шаблонные методы — так сразу вы покроете его тестами и так сразу проверите жизнеспособность реализации The Template Method Pattern.
Как мне кажется вы говорите в большей степени про функциональное или про тестирование «черного ящика». Я же про про unit-тестирование, которое обеспечит мне покрытие кода тестами и возможность рефакторинга. В вашем случае тестирование только API класса покрытия не даст, вы можете лишь проверить результат. К тому же, если вариантов много, что бы протестировать все ветвления алгоритма вы будете вынуждены иметь целый набор эталонов для всех случаев. Так что выбирайте сами, что вам приемлемо в конкретной ситуации.
Я как раз и говорю о модульных тестах (которые unit-тесты).

Если целиком и полностью следовать идеологии TDD, тогда у вас защищённые и приватные методы могут появиться только в результате рефакторингов публичных методов, которые уже были протестированы.

«Я же про про unit-тестирование, которое обеспечит мне покрытие кода тестами и возможность рефакторинга.»
Возможность рефакторинга, как раз, ваши тесты и не обеспечат, по определению. Почему? Потому что рефакторинг, это изменение реализации кода без изменения его поведения. Таким образом, если в результате рефакторинга вы вдруг внезапно захотите отказаться от The Template Method, тогда ваши тесты внезапно поломаются, несмотря на то, что поведение кода осталось тем же самым.
Если целиком и полностью следовать идеологии TDD...

Тут вопрос не в TDD. Помимо его есть еще и TAD (Test-After Development) и в примере написан был cначал код класса, а потом уже тесты.

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

Это по какому определению? Если, есть гарантия, что я ничего не отломаю (а это обеспечивает наличие тестов), то я смело могу изменять структуру внутренних классов. Как реализованы тесты к возможности рефакторинга отношения не имеет. Согласен, что в теории иметь тесты только на интерфейс хорошо и удобно, но в реальности протестировать все сценарии поведения, затрагивая только public-методы может быть слишком накладно или вообще невозможно. А уж обеспечить хорошее покрытие кода вообще нереально.
«Если, есть гарантия, что я ничего не отломаю (а это обеспечивает наличие тестов), то я смело могу изменять структуру внутренних классов.»
тогда это не рефакторинг :-)

«затрагивая только public-методы может быть слишком накладно или вообще невозможно»
ненакладно и вполне реально

так или иначе — я свою точку зрения, которая основывается на моём опыте высказал.
… изменять структуру внутренних классов.»
тогда это не рефакторинг :-)

А что тогда?
Рефакторинг (англ. refactoring) — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы
«затрагивая только public-методы может быть слишком накладно или вообще невозможно»
ненакладно и вполне реально

Рад за вас, видимо у нас с вами разный класс решаемых задач.
«А что тогда? „
Вот именно — вы не можете изменить внутреннюю структуру, потому что вы завязаны на The Template Pattern ;-)
Вы не можете изменить реализацию произвольным образом — потому что ваши тесты знают о деталях реализации.
Насчет рефакторинга все же с вами не соглашусь.
Допустим, хочу сделать в нашем примере ExtractMethod для алгоритма — делается это примерно так:
1. Выделяю класс и пишу метод на его создание
2. Тестирую метод на создание экземпляра нового класса
3. Создаю новую TestFixture для выделенного класса и переношу туда тесты на алгоритм
4. В исходном тесте проверяю, что вызов был делегирован новому классу.
Ну, или в другой последовательности, если вы используете TDD

Ну вижу смысла продолжать спор, раз вы против white-box testing, тем не менее спасибо за то, что выразили свою точку зрения.
Я говорил не о конкретных техниках рефакторинга, а о рефакторинге в общем.

Сейчас вы отказаться от The Template Method не сможете, потому что на этот паттерн у вас завязаны тесты. А вот избавление от него, с переписыванием в пользу какого-то другого решения, и есть рефакторинг, который у вас провести не получится.
Да все получится ;). Возникнет необходимость реструктурировать код — итеративно рефакторите вместе с тестами, вот и все. Вы рассматривайте unit-тесты как что каноническое, а они так же как и код имеют тенденцию изменяться в рамках роста проекта. Это же не приемочные тесты заказчика точный на вход-выход. Модульное тестирование подразумевает полное покрытие кода (в идеале всех методов), поэтому если код меняется — соответственно и тесты по необходимости. И нужны они в первую очередь разработчику.
Я и правда подхожу к тестам слишком академично.

Для меня рефакторинг — это изменение кода без изменения поведения и, как следствие, без изменения тестов. Если нужно менять тесты — значит изменилось поведение, значит это уже не рефакторинг :-)
Да, я уловил сейчас вашу мысль. Если рассматривать код отдельно от тестов, то все нормально — рефакториг кода провести можно. Вы рассматриваете код + тесты вместе, поэтому по терминологии это уже как бы и не рефакторинг. Все же объект рефакторинга — класс, а не тесты, и при смене шаблонного метода его интерфейс остается прежним, так что все же рефакторинг на мой взгляд ;)
А как вы делаете дугообразные скриншоты? поделитесь магией =)
Здорово, я для виндузятника есть нечто похожее? Просто линух редко под рукой…
Каюсь, ступил. GIMP можно и на венде юзать =)
Snagit — великолепная программа
Это да. Но вот волна для обрезания там всё-таки грубовата, на мой взгляд. Так что для этого мы используем GIMP, как уже было сказано выше :-)
Зарегистрируйтесь на Хабре , чтобы оставить комментарий