Я недавно начал практиковать TDD и модульное тестирование, и мои главные учебные пособия - отличный GOOSGBT и прочтение вопросов с тегами TDD здесь, на SO.
Иногда используемый мной процесс создает класс «контроллера» - как правило, класс, который является фасадом над довольно сложной подсистемой, в которой по мере роста числа функций, реализованных в подсистеме, обязанности постоянно вытесняются.в вспомогательные классы до тех пор, пока исходный класс по существу не будет нести никаких обязанностей, кроме как делать правильные вызовы небольшому набору классов-соавторов и пересылать возвращаемую информацию (если есть) другим классам-соавторам.
Первоначально тесты дляклассы будущих контроллеров были написаны на уровне намерений конечных пользователей класса: «Если я сделаю этот вызов, какими должны быть наблюдаемые эффекты, которые меня, конечного пользователя класса, действительно волнуют?».Но по мере того, как все больше обязанностей и тестов для пограничных случаев распределялись по вспомогательным классам (которые заменяются тестовыми двойниками в тестах для класса контроллера), эти тесты стали казаться действительно ... неопределенными и неспецифичными:были неизменно "счастливые пути" тесты, которые на самом деле, казалось, не дошли до сути вопроса.Трудно объяснить, что я имею в виду, но при чтении тестов у меня появилось что-то вроде «Ну и что? Почему вы выбрали именно этот тест« счастливый путь », а не какой-либо другой? Каково значение?Test точно определить точную причину, почему код теперь не работает? "Со временем я все более и более склонялся к тому, чтобы вместо этого написать тесты с точки зрения того, как сотрудники классов должны использоваться вместе: «класс должен вызвать этот метод для этого сотрудника и передать результат этому другомусотрудник ", который дал гораздо более сфокусированный, описательный и четко мотивированный набор тестов (и, как правило, результирующий набор тестов невелик).
Это, очевидно, имеет свои недостатки: тесты теперь сильно связаны со спецификой реализации класса контроллера, а не с гораздо более гибким «что бы конечный пользователь этого класса увидел, что ему не все равнооколо?".Но на самом деле тесты уже достаточно связаны с ним в силу того факта, что они должны настроить всех коллабораторов Test Double так, чтобы они вели себя точно так, как требует реализация, чтобы дать правильные результаты от конечного пользователя классов.точка зрения.
Итак, мой вопрос: находят ли коллеги из TDD, что меньшинство классов мало что делает, а собирает свой (маленький) набор соавторов?Считаете ли вы хранение тестов для таких классов написанным с точки зрения конечных пользователей неточным и неудовлетворительным, и если да, то допустимо ли писать тесты для таких классов явно с точки зрения того, как он вызывает ипередает данные между своими сотрудниками?
Надеюсь, здесь достаточно понятно, к чему я клоню!:)
В качестве конкретного примера: одним из практических проектов, над которым я работал, был загрузчик / просмотрщик телепередач (если вы когда-либо видели «Digiguide», вы будете знать, что яимею в виду), и я реализовывал основную часть приложения - ту, которая фактически обновляет списки по сети и интегрирует недавно загруженные списки в текущий набор известных телевизионных программ.Интерфейсом для этой (на удивление сложной, когда все требования учитывались) функции был класс с именем ListingsUpdater, у которого был метод с именем "updateListings".
Теперь конечные пользователи ListingsUpdater действительно заботятся только о нескольких вещах: после того, как listsUpdate был вызван, корректна ли база данных списков телепередач, и были ли внесены изменения в базу данных (добавление телепрограмм, изменение их при трансляции?) произошли изменения и т. д.) описаны ли слушатели изменений? Когда реализация была очень, очень простой, сделкой типа «притворяйся, пока не сделаешь», это работало нормально: но по мере того, как я постепенно продвигал реализацию к той, которая будет работать в реальном мире, «настоящая работа» стала все дальше и дальше от ListingsUpdater до тех пор, пока он в основном не собрал нескольких соавторов: ListingsRequestPreparer для оценки текущего состояния листингов и создания HTTP-запросов для ListingsDownloader и ListingsIntegrator, который распаковал недавно загруженные листинги и включил их (он тоже делегировал сотрудникам) в базу данных списков. Теперь обратите внимание, что для того, чтобы выполнить контракт ListingsUpdater с точки зрения пользователя, я должен в тесте дать указание его DoubleIntegrator Test Double заполнить (поддельную) базу данных правильными данными (!), Что кажется странным. Гораздо разумнее отбросить тесты «с точки зрения конечного пользователя ListingsUpdater» и вместо этого добавить тест, который говорит, что «когда ListingsDownloader загрузил новые листинги, убедитесь, что они переданы в ListingsIntegrator».