Комментарии 64
Спасибо.
Спасибо! Очень интересно!
Инспектор (The Inspector) — пожалуй самый неприятный момент в тестировании.
Имея некоторый опыт в TDD, я бы предложил сосредотачиваться на паттернах, а проще говоря на рекомендация о том, как НАДО писать тесты.
И не придумывать дурацкие названия антипаттернам — мне трудно представить, что в лексиконе разработчиков появляются фразы вроде «Тест Успех любой ценой», «Тест Жадный ловец» и т.п.
И не придумывать дурацкие названия антипаттернам — мне трудно представить, что в лексиконе разработчиков появляются фразы вроде «Тест Успех любой ценой», «Тест Жадный ловец» и т.п.
Знание паттернов (как надо) и анти-паттернов (как не надо), по-моему, очень хорошо дополняют друг друга. Я и сам предпочитаю более строгие названия, но перевод есть перевод. Кроме того, специально оставил первоначальные названия на английском, которые субъективно воспринимаются чуть более серьёзно. ;)
Хочется сделать комплимент и автору и Вам: офигенные названия паттернов и отличный перевод!
Жаль, сложно держать все анти-паттерны в голове…
Жаль, сложно держать все анти-паттерны в голове…
Очень толково расписаны большинство потенциальных «вил» в test-driven development. Неплохой перевод — спасибо за пост!
Было бы неплохо, если бы мы обсудили здесь паттерны/анти-паттерны проектирования, которые мешают написанию правильных тестов. Начну первым.
1) Singleton — ужаснейший анти-паттерн в тестировании и разработке. Чрезмерное использование синглетонов в проекте — очень… очень плохое решение.
2) «Недоношенный» обьект — когда после вызова конструктора по-прежнему необходима допололнительная инициализация… в виде вызовов init(), start(), polulate(...) Это всё Гнилой код.
Продолжаем список…
1) Singleton — ужаснейший анти-паттерн в тестировании и разработке. Чрезмерное использование синглетонов в проекте — очень… очень плохое решение.
2) «Недоношенный» обьект — когда после вызова конструктора по-прежнему необходима допололнительная инициализация… в виде вызовов init(), start(), polulate(...) Это всё Гнилой код.
Продолжаем список…
а что рекомендуется вместо синглтона? когда нужен один объект глобальный? :)
Я не согласен что синглтон анти-паттерн, но вполне представляю что его можно заменить например так:
при использовании синглтона мы пишем допустим так DownloadManager.Instance.Progress, чтобы получить прогресс в нашем методе
без синглтона тоже самое можно достичь следующим образом:
создаем экземпляр DownloadManager и подсовываем его в некоторые сервисные функции Services.GetDownloadProgress(downloadManager), то есть один и тот же объект кочует по сервисам итд
Но это ИМХО!
при использовании синглтона мы пишем допустим так DownloadManager.Instance.Progress, чтобы получить прогресс в нашем методе
без синглтона тоже самое можно достичь следующим образом:
создаем экземпляр DownloadManager и подсовываем его в некоторые сервисные функции Services.GetDownloadProgress(downloadManager), то есть один и тот же объект кочует по сервисам итд
Но это ИМХО!
создается кем? по сути всё равно должен быть некий «рулящий» объект, сделанный тем же синглтоном, но раздающий определенные объекты по запросам (а-ля фабрика объектов). но от синглтона тут тоже не уйти :)
Нет, от синглтона мы полностью уходим, потому что создаем один экземпляр объетка в сборке и передаем его во все методы или классы где хотим его использовать.
допустим есть класс которому для работы нужно соединение с базой
с синглтоном:
у нас есть класс MyClass у которого конструктор без параметров, и внутри работы он юзает синглтон
без синглтона:
у нас есть класс MyClass, но конструктор у него принимает параметором соединение с базой, и внутри работы использует уже его
допустим есть класс которому для работы нужно соединение с базой
с синглтоном:
у нас есть класс MyClass у которого конструктор без параметров, и внутри работы он юзает синглтон
без синглтона:
у нас есть класс MyClass, но конструктор у него принимает параметором соединение с базой, и внутри работы использует уже его
когда таких классов 2-3 десятка, и каждому надо по 2-3 синглтона… я сочувствую архитектору такой системы :)
и ответ я так и не увидел — где создается один экземпляр объекта?
и ответ я так и не увидел — где создается один экземпляр объекта?
ну в любом мест сборки создаешь перед созданием классов где будешь использовать этот объект
я сам не против синглтонов если вмеру
я сам не против синглтонов если вмеру
Единственный экземпляр самому создавать не надо. Он либо уже у вас есть(пришел в качестве параметра конструктора или метода — Dependency Injection), либо вы его получаете через посредника (Service Locator).
а Service Locator у нас не синглтон ли случайно? если нет — то что это за объект?
какая разница что я передам объект через конструктор или запрошу его же из некоего реестра где хранятся объекты?
если я буду передавать объекты базы данных например через конструктор, а потом кто-то после меня решит это дело поправить — сколько же ему кода придется перелопатить чтобы везде изменить ?:)
если я буду передавать объекты базы данных например через конструктор, а потом кто-то после меня решит это дело поправить — сколько же ему кода придется перелопатить чтобы везде изменить ?:)
Тем самым мы увеличим связаность, что тоже не гуд. Да и как быть с ленивой инициализацией, например тоже подключение к бд может и непонадобится, если всё уже есть в кеше.
Чрезмерное избегание некоторых антипатернов ведёт к ещё большему засорению кода.
С опытом приходит понимание, что антипатерны и денормализация — это не всегда зло.
Чрезмерное избегание некоторых антипатернов ведёт к ещё большему засорению кода.
С опытом приходит понимание, что антипатерны и денормализация — это не всегда зло.
Ну у симфони в их компоненте dependency injection проблема ленивой авторизации решается. У вас есть некоторый объект Context, который передаётся в конструкторы и хранится внутри объекта. Когда вам понадобится класс бд вызываете метод Context->getDBClass, например, и именно тогда создаётся экземпляр класса бд. При повторном вызове метода естественно возвращается ссылка на тот же экземпляр что и раньше.
смысл понятия «анти-паттерн» вовсе не в том, что он однозначно вреден и должен изничтожаться. «анти-паттерны» обычно терпимы в единичных случаях и представляют опасность, когда их много и архитектура приложения серьезно на них опирается.
конкретно касаемо синглотона: picocontainer.org/singleton-antipattern.html
конкретно касаемо синглотона: picocontainer.org/singleton-antipattern.html
Хотелось бы поспорить на счет «недоношенного» объекта. У меня как раз есть такой =)
В моем случае — это сложный десктопный (не веб) элемент управления, взаимодействующий с внешними источниками данных и другим окружением на форме. Критическими для его работы являются около 20 свойств, если они не назначены, или назначены неправильно, элемент работать не может (падает он при этом, или просто отказывается работать — другой вопрос). Если делать этот элемент «самозапускаемым», то процедура назначения каждого свойства должна будет инициировать проверку полноты и целостности всего комплекта свойств. Вил не будет, зато будут грабли. Т.к. придется писать и тестировать отдельный кусок кода, который сможет оказаться нереентерабельным, чувствительным к порядку назначения свойств или еще что-нибудь…
Уж лучше, на мой взгляд, в этой ситуации оставить метод Activate(), который программист гарантированно вызовет только после того, как все свойства правильно назначены.
В моем случае — это сложный десктопный (не веб) элемент управления, взаимодействующий с внешними источниками данных и другим окружением на форме. Критическими для его работы являются около 20 свойств, если они не назначены, или назначены неправильно, элемент работать не может (падает он при этом, или просто отказывается работать — другой вопрос). Если делать этот элемент «самозапускаемым», то процедура назначения каждого свойства должна будет инициировать проверку полноты и целостности всего комплекта свойств. Вил не будет, зато будут грабли. Т.к. придется писать и тестировать отдельный кусок кода, который сможет оказаться нереентерабельным, чувствительным к порядку назначения свойств или еще что-нибудь…
Уж лучше, на мой взгляд, в этой ситуации оставить метод Activate(), который программист гарантированно вызовет только после того, как все свойства правильно назначены.
Можно и попорить =)
Расскажите-ка нам по-подробнее о процедуре тестирования вашего «недоношенного» объекта с 20 свойствами, взаимодействующего с внешними источниками данных и другим окружением на форме? Хотелось бы узнать, как выглядят тесты для такого монстра?
Расскажите-ка нам по-подробнее о процедуре тестирования вашего «недоношенного» объекта с 20 свойствами, взаимодействующего с внешними источниками данных и другим окружением на форме? Хотелось бы узнать, как выглядят тесты для такого монстра?
К сожалению, не смогу порадовать какой-нибудь интересной новой методикой, т.к. у нас не индустриальное программирование, а программирование несколько «домашнее».
Упор сделан не на функциональное тестирование, а на структурное. Мы конечно не привлекали теорию алгоритмов в явном виде и не рисовали полный конечный автомат состояний объекта, но очень много времени уделили разрисовке вариантов использования. Тестированием занимались те же люди, что и разработкой (хотя теория тестирования этого делать не рекомендует), поэтому тестирующие понимали наиболее опасные места и все варианты развития событий и внимательно по ним прошлись.
А поскольку описываемый мною класс является элементом управления, правильность его работы видна «на глазок».
Упор сделан не на функциональное тестирование, а на структурное. Мы конечно не привлекали теорию алгоритмов в явном виде и не рисовали полный конечный автомат состояний объекта, но очень много времени уделили разрисовке вариантов использования. Тестированием занимались те же люди, что и разработкой (хотя теория тестирования этого делать не рекомендует), поэтому тестирующие понимали наиболее опасные места и все варианты развития событий и внимательно по ним прошлись.
А поскольку описываемый мною класс является элементом управления, правильность его работы видна «на глазок».
Добавлю, что обилие классов и/или объектов, которые требуют особого метода активации действительно может внести неразбериху в код, но если этот метод будет только у одного-двух ключевых объектов, на задействовании которых все равно сосредоточена значительная часть внимания разработчика, то запуск методом не создает заметных неудобств.
Более того, я могу себе позволить определенные программистические вольности, пока объект уже создан, но еще не активирован.
Более того, я могу себе позволить определенные программистические вольности, пока объект уже создан, но еще не активирован.
Поясните, пожалуйста, почему 2) плох именно для тестов.
Второй подход плох в принципе… init() метод можно 1) забыть вызвать, можно 2) вызвать 2жды, а можно и 3) не успеть вызвать(в моногопоточной среде).
Обьект должен рождаться «полноценным» либо «мертвым»(с выбросом исключения). Это упрощает процедуру инициализации приложения и уменьшает количество тесткейсов(вам не придётся писать тесты для случаев 1,2,3).
Обьект должен рождаться «полноценным» либо «мертвым»(с выбросом исключения). Это упрощает процедуру инициализации приложения и уменьшает количество тесткейсов(вам не придётся писать тесты для случаев 1,2,3).
Я согласен, что использовать класс с методом-активатором менее удобно и безопасно, чем класс, который вступает в работу сам.
Но с другой стороны недостатки 1), 2) и 3) преувеличены.
1) Можно забыть выполнить любое действие, от инициализации локальной переменной до бэкапа корпоративной БД.
2) Можно любое действие случайно выполнить два раза )
3) А можно и сам объект не успеть создать… нужно вдумчиво подойти к планированию межпоточных блокировок. Полагаться на упорядоченность асинхронных действий все равно никогда нельзя. Это же, так называемые «состязания», которые потенциально опасны с точки зрения правильности работы.
Ну вот как родить полноценный объект? В моем случае? Конструктор с 20 параметрами делать что ли.
Кроме того, сама идеология написания и использования пользовательских элементов управления предписывает вызов конструктора класса без параметров, а потом назначение ему всех свойств в (условно) произвольном порядке. Как объект поймет, что его уже «запрограммировали»?
А если давать ему пытаться запуститься при каждом изменении ключевого слова, то процедура активации (которая все равно будет присутствовать в классе, только не открытая, а закрытая) будет пестреть IsNothing'ами, <>0, <>"" и прочими проверялками введенности переменных.
Но с другой стороны недостатки 1), 2) и 3) преувеличены.
1) Можно забыть выполнить любое действие, от инициализации локальной переменной до бэкапа корпоративной БД.
2) Можно любое действие случайно выполнить два раза )
3) А можно и сам объект не успеть создать… нужно вдумчиво подойти к планированию межпоточных блокировок. Полагаться на упорядоченность асинхронных действий все равно никогда нельзя. Это же, так называемые «состязания», которые потенциально опасны с точки зрения правильности работы.
Ну вот как родить полноценный объект? В моем случае? Конструктор с 20 параметрами делать что ли.
Кроме того, сама идеология написания и использования пользовательских элементов управления предписывает вызов конструктора класса без параметров, а потом назначение ему всех свойств в (условно) произвольном порядке. Как объект поймет, что его уже «запрограммировали»?
А если давать ему пытаться запуститься при каждом изменении ключевого слова, то процедура активации (которая все равно будет присутствовать в классе, только не открытая, а закрытая) будет пестреть IsNothing'ами, <>0, <>"" и прочими проверялками введенности переменных.
Попробуйте разделить ваш обьект на несколько более мелких частей. Часть свойств можно комбинировать. А ля…
Это первое, что пришло в голову. Дальше нужно знать специфику вашего контрола =))
new MyObject(Panel parent, MyObject.STYLE_FLAT|MyObject.STYLE_RIGHT|MyObject.COLOR_RED).
Это первое, что пришло в голову. Дальше нужно знать специфику вашего контрола =))
А мы и разделили. Этот элемент управления имеет внутри себя еще собственную иерархию объектов, включая и пользовательские (наши же). Но все равно на него ложится много работы.
Скрывать нечего — это элемент для отображения ленточных форм данных (как в Access) (т.е. это довольно нестандарный объект, к тому же очень влиятельный чувствительный к происходящему на форме одновременно).
Свойства можно комбинировать, можно их группировать в структуры, но это не спасет от явного их указания и более высоких требований к их подготовке.
Скрывать нечего — это элемент для отображения ленточных форм данных (как в Access) (т.е. это довольно нестандарный объект, к тому же очень влиятельный чувствительный к происходящему на форме одновременно).
Свойства можно комбинировать, можно их группировать в структуры, но это не спасет от явного их указания и более высоких требований к их подготовке.
> 2) «Недоношенный» обьект — когда после вызова конструктора по-прежнему необходима допололнительная > инициализация… в виде вызовов init(), start(), polulate(...) Это всё Гнилой код.
Скажите это программистам на С++ (а как же эксепшены в конструкторах?), или программистам на Embedded C++ (что такое конструктор? :-D) и они вам скажут, насколько вы заблуждаетесь.
Скажите это программистам на С++ (а как же эксепшены в конструкторах?), или программистам на Embedded C++ (что такое конструктор? :-D) и они вам скажут, насколько вы заблуждаетесь.
Могу сказать и С++ программистам (простите меня, что о вас совсем забыл). Правда, что бы перефразировать сказанное, мне придётся немного подучить конструкторы в С++ =)).
Может сами попробуете переформулировать пункт №2 в терминах С++? Было бы здорово =))
Может сами попробуете переформулировать пункт №2 в терминах С++? Было бы здорово =))
Подробности тут: habrahabr.ru/blogs/development/43761/#comment_1089228
А вообще умные дядьки от С++ (Герб Саттер кажется), дают один простой совет: Не кидайте исключения в конструкторах классов. Т.е. конструктор должен выполнять минимальные действия по созданию и инициализации объекта. Если нужно выполнять какие-то более сложные действия, лучше иметь отдельный метод для этого. Собственно создавать «недоношенный объект».
Конечно же в Java все не так, там если что gc сам подчистит.
А вообще умные дядьки от С++ (Герб Саттер кажется), дают один простой совет: Не кидайте исключения в конструкторах классов. Т.е. конструктор должен выполнять минимальные действия по созданию и инициализации объекта. Если нужно выполнять какие-то более сложные действия, лучше иметь отдельный метод для этого. Собственно создавать «недоношенный объект».
Конечно же в Java все не так, там если что gc сам подчистит.
Нет, проблемы огромные и RAII здесь не причем.
Объект считается созданным полностью тогда, когда отработал его конструктор. Деструктор объекта вызывается только в том случае, когда объект полностью создан.
Итого, если объект создан на стеке, то при исключении в конструкторе, деструктор вызван не будет. И если вы выделяли какие-то ресурсы в конструкторе, а потом произошло исключение, то деструктор вызван не будет, следовательно произойдет утечка ресурсов.
Если объект был создан на куче, то возникает проблема не только с вызовом деструктора, но и с освобождением памяти.
try {
MyType *myObj = new MyType(); // исключение
catch(...)
{
// обработали исключение
}
// чему будет равен myObj и как его тут удалить?
Ответ — никак. Память неявно должен высвобождать компилятор, на деле он этого не делает.
В Embedded отсутствует placement new как класс, а глобальный оператор new перегружен, потому что аллокаторы стандартной библиотеки не подходят до случая embedded (к примеру если дело происходит в ядре).
Объект считается созданным полностью тогда, когда отработал его конструктор. Деструктор объекта вызывается только в том случае, когда объект полностью создан.
Итого, если объект создан на стеке, то при исключении в конструкторе, деструктор вызван не будет. И если вы выделяли какие-то ресурсы в конструкторе, а потом произошло исключение, то деструктор вызван не будет, следовательно произойдет утечка ресурсов.
Если объект был создан на куче, то возникает проблема не только с вызовом деструктора, но и с освобождением памяти.
try {
MyType *myObj = new MyType(); // исключение
catch(...)
{
// обработали исключение
}
// чему будет равен myObj и как его тут удалить?
Ответ — никак. Память неявно должен высвобождать компилятор, на деле он этого не делает.
В Embedded отсутствует placement new как класс, а глобальный оператор new перегружен, потому что аллокаторы стандартной библиотеки не подходят до случая embedded (к примеру если дело происходит в ядре).
Тем, не менее, можно выбросить какое-нибудь фатальное исключение, которое точно завершит программу, и проблема с памятью будет неактуальтной. Или принять принцип — *все* объекты должны иметь метод init(). Но не так, что одни —надо инициализировать, а другие нет, это порождает путаницу. А вообще по ходу, это серьезный недостаток С++, и каждый изобретает тут свои шрабли, печально.
Закономерный вопрос «Где выбросить-то»? Любое «фатальное» исключение можно словить, если уж не стандартными средствами, то SEH'ом. И вот тут я не вижу недостатков С++. Они конечно же в нем есть и в большом кол-ве, но это тема для отдельного разговора.
А вообще, подобные вещи, как пункт 2 — слишком специфичны для конкретной ситуации. И нельзя сказать, что делать метод init() для инициализации объекта — это дурной тон. Не сколько не дурной, главное соблюдать единый стиль, чтобы не было путаницы.
А вообще, подобные вещи, как пункт 2 — слишком специфичны для конкретной ситуации. И нельзя сказать, что делать метод init() для инициализации объекта — это дурной тон. Не сколько не дурной, главное соблюдать единый стиль, чтобы не было путаницы.
эээ А как насчет шаблона Factory или подобного для «недоношенных» объектов?
Эээ… бороться с антипаттернами — хорошо, но что делать, когда синглтон нужен? Например — класс для доступа к БД, для записи в DebugLog, ведь эти классы могут понадобиться в любом месте, и что делать? передавать что ли экземпляры каждому (!) объекту в конструкторе, или как?
Мне кажется вам, или кому-то следует оформить отдельную статью, про анти-паттерны, написать известные вам, попросить дополнять в комментариях, и вносить их по мере добавления в статью, чтобы людям, котоыре хотят получить данную информацию, было проще найти то, что нужно.
Спасибо, интересная статья!
Очень интерестно и полезно, спасибо!
Надеюсь на продолжение темы анти-паттернов.
Надеюсь на продолжение темы анти-паттернов.
Отличная статья! Хороший перевод :)
(убежал навешивать ярлыки на свои тесты...)
(убежал навешивать ярлыки на свои тесты...)
Что мне всегда нравилось антипаттернах, так это их названия. Звучат как музыка:)
Это из какой рпг персонажи описаны? ;)
Вот! Вот что меня терзало в TDD! Я чувствовал в нем саму возможность таких антипаттернов и поэтому понимал возможности ошибок и как следствие посчитал тестирование неэффективным. Особенно лень было править тесты при каждом рефакторинге системы.
Тогда вообще лучше не программировать — антипаттернов программирования куда больше :)
А в чем сложности с правкой тестов при рефакторинге? Рефакторинг по определению не должен менять поведение системы, значит, тесты обновлять не сложнее, чем обычный код. А если рефакторинг проводится автоматическими средствами, то тем более.
Сложности как раз может вызывать антипаттерн «Инспектор», но на то он и анти.
А в чем сложности с правкой тестов при рефакторинге? Рефакторинг по определению не должен менять поведение системы, значит, тесты обновлять не сложнее, чем обычный код. А если рефакторинг проводится автоматическими средствами, то тем более.
Сложности как раз может вызывать антипаттерн «Инспектор», но на то он и анти.
Мой любимый — заяц.
А автору неплохо бы сделать древовидную компоновку, а то простыня из десятков ошибок быстро вылетает из головы.
А автору неплохо бы сделать древовидную компоновку, а то простыня из десятков ошибок быстро вылетает из головы.
Что то в этом есть поэтическое
Похоже
«Падающий лист при рассвете в зимнее утро»… типа этого :)
Спасибо
Похоже
«Падающий лист при рассвете в зимнее утро»… типа этого :)
Спасибо
Есть ли книги, которые стоит посоветовать для хорошего понимания TDD?
JUnit Recipes
Practical Methods for Programmer Testing
Practical Methods for Programmer Testing
Джерард Месарош «Шаблоны тестирования xUnit: рефакторинг кода тестов», Signature Series, изд. Вильямс, 2008, 832 стр., с ил.; ISBN 978-5-8459-1448-4, 978-0-13-149505-0.
Где-то я уже вроде видел это, вроде даже в переводе, но не так полно. Спасибо.
По переводу — «The Peeping Tom» вероятно в русском соответствует идиоме «любопытная Варвара»?
По переводу — «The Peeping Tom» вероятно в русском соответствует идиоме «любопытная Варвара»?
НЛО прилетело и опубликовало эту надпись здесь
Нормательно, мне понравилось ))
спасибо что собрали их как хорошо что есть место куда можно отправить своих друзей учить/читать
Спасибо, ценная инфа и отличный перевод: чувствуется хорошее владение родным языком. Это я вам говорю как структуральный лингвист-любитель :)
Каталог антипаттернов xUnit тестирования в книге Effective Unit Testing.
www.manning.com/koskela2/
www.manning.com/koskela2/
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Анти-паттерны Test Driven Development