TDD - высокоуровневая функция имеет слишком много насмешек. Я должен даже потрудиться тестировать его?

У меня есть приложение.NET с веб-фронтендом, бэкендом службы Windows WCF. Приложение довольно просто - требуется некоторый ввод данных пользователем, отправляя его сервису. Сервис делает это - берет вход (электронная таблица Excel), извлекает элементы данных, проверяет SQL DB, чтобы удостовериться, что объекты не являются уже существующими - если они не существуют, мы выполняем запрос в реальном времени стороннему поставщику данных и получаем результаты, вставляя их в базу данных. Это делает некоторый вход по пути.

У меня есть класс Job с единственной общественностью ctor и общедоступным Выполнением () метод. ctor берет все параметрические усилители и Выполнение (), метод делает всю вышеупомянутую логику. Каждая логическая часть функциональности разделяется на отдельный класс - IParser действительно регистрирует парсинг, IConnection делает взаимодействие с поставщиком данных, IDataAccess делает доступ к данным, и т.д. класс Job имеет частные экземпляры этих интерфейсов, и использует DI для построения фактической реализации по умолчанию, но позволяет пользователю класса вводить любой интерфейс.

В реальном коде я использую значение по умолчанию ctor. В моих модульных тестах на Выполнение () метод, я использую все создание фиктивных объектов через NMock2.0. Это Выполнение () метод является по существу 'высокоуровневой' функцией этого приложения.

Теперь вот моя проблема / вопрос: модульные тесты на это Выполнение () метод являются сумасшедшими. У меня есть три фиктивных объекта, которые я отправляю в ctor, и каждый фиктивный объект устанавливает ожидания по себе. В конце я проверяю. У меня есть несколько различных потоков, которые метод Выполнения может взять, каждый поток, имеющий его собственный тест - он мог найти, что все уже находится в базе данных, и не выполняют запрос поставщику..., или исключение могло быть выдано, и состояние задания могло быть установлено на 'неудавшийся'... ИЛИ у нас может быть случай, где мы не имели данных и должны были выполнить запрос поставщика (таким образом, все те вызовы функции должны будут быть сделаны).

Теперь - прежде чем Вы будете вопить на меня и говорить 'свое Выполнение () метод является слишком сложным!' - этот метод Выполнения является только всего 50 строками кода! (Это действительно выполняет вызовы к некоторой закрытой функции; но весь класс является только 160 строками). Так как вся 'реальная' логика делается в интерфейсах, которые объявляются на этом классе. однако, самый большой модульный тест на этой функции является 80 строками кода с 13 вызовами для Ожидания. ВЗДОР ().. _

Это делает рефакторинг огромной боли. Если я хочу изменить это Выполнение () метод вокруг, я должен пойти, редактируют мои три модульных теста и добавляют/удаляют/обновляют, Ожидают () вызовы. Когда я должен осуществить рефакторинг, я заканчиваю тем, что провел больше времени, создавая мои ложные вызовы, чем я делал на самом деле записи нового кода. И выполнение реального TDD на этой функции делает это еще более трудным если не невозможный. Это заставляет меня думать, что это даже не стоит поблочного тестирования эта высокоуровневая функция вообще, так как действительно этот класс не делает большой логики, это просто раздает данные к своим составным объектам (которые являются всеми полностью протестированная единица и не требуют насмешки).

Таким образом - я должен даже потрудиться тестировать эту функцию высокого уровня? И что я получаю путем выполнения этого? Или я полностью неправильно использую ложные/тупиковые объекты здесь? Возможно, я должен фрагментировать модульные тесты на этом классе и вместо этого просто сделать автоматизированный интеграционный тест, который использует реальные реализации объектов и Утверждает () против SQL-запросов, чтобы удостовериться, что правильные данные конечного состояния существуют? Что я пропускаю здесь?

Править: Вот код - первая функция является фактическим Выполнением () метод - затем мои пять тестов, которые тестируют все пять возможных путей выполнения кода. Я изменил его некоторые по причинам NDA, но общее понятие все еще там. Что-нибудь, что Вы видите неправильно с тем, как я тестирую эту функцию, какие-либо предложения, на что измениться для создания ее лучше? Спасибо.

7
задан Rodia 2 April 2017 в 19:38
поделиться

8 ответов

Я думаю, что мой совет эхом большинство из чего здесь размещен.

Это звучит, как если ваш метод прогона должен быть разбит больше. Если его дизайн заставляет вас в тесты, которые более сложны, чем это, что-то не так. Помните, что это TDD, о котором мы говорим, поэтому ваши тесты должны диктовать дизайн вашей рутины. Если это означает, что тестирование частных функций, так что будь то. Нет технологической философии или методологии не должно быть настолько жестко, что вы не можете делать то, что чувствует правильно.

Дополнительно я согласен с некоторыми другими плакатами, что ваши тесты должны быть разбиты на более мелкие сегменты. Спросите себя, если вы собираетесь написать это приложение в первый раз, и ваша функция Run еще не существует, как бы выглядели ваши тесты? Этот ответ, вероятно, не то, что у вас есть в настоящее время (в противном случае вы не задаете вопрос). :)

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

Редактировать

Просто увидел, что вы разместили код и имели некоторые мысли (нет конкретного порядка).

  1. Слишком много кода (IMO) внутри вашего блока Synclock. Общее правило состоит в том, чтобы держать код минимальному внутри синхронизации. Это все должно быть заблокировано?
  2. Начните разбивать код в функции, которые можно проверить независимо. Пример: forloop, который удаляет идентификатор из списка (строка), если они существуют в БД. Некоторые могут утверждать, что Call m_dao.beginjob должен быть в какой-то функции GetID, которая может быть проверена.
  3. Может ли любой из процедур m_dao быть превращен в функции, которые могут проверить самостоятельно? Я бы предположил, что у класса M_DAO есть какие-то тесты, но, глядя на код, кажется, что это может быть не так. Они должны вместе с функциональностью в классе M_Parser. Это облегчает часть бремени испытаний прогона.

Если бы это был мой код, моя цель заключалась в том, чтобы получить код в место, где все отдельные процедуры вызовы внутри прогона, проходят тестирование самостоятельно и что тесты прогона просто тестируют финальный выход. Данный ввод A, B, C: ожидаемый результат X. Дайте ввод E, F, G: ждут Y. Деталь того, как запускается до X или Y, уже тестируется в тестах других процедур.

Это были только мои рыжие мысли. Я уверен, что есть куча разных подходов, которые можно взять.

4
ответ дан 7 December 2019 в 01:21
поделиться

Обычно я предлагаю попробовать функцию startToDataFrame () , но я считаю, что это будет довольно сложно, потому что она не очень хорошо структурирована для начала.

Я бы порекомендовал работать с этой функцией:

xmlToList(books)

Одна проблема состоит в том, что на книгу приходится несколько авторов, поэтому вам нужно будет решить, как с этим справиться при структурировании кадра данных.

После того, как вы решили, что делать с вопросом нескольких авторов, то это довольно прямо вперед, чтобы превратить вашу книгу список в кадр данных с функцией ldply () в plyr (или просто использовать plapply и преобразовать возвращаемое значение в data.frame с помощью do.call («rbind»...).

Вот полный пример (исключая автора):

library(XML)
books <-  "w3schools.com/xsl/books.xml"
library(plyr)
ldply(xmlToList(books), function(x) { data.frame(x[!names(x)=="author"]) } )

   .id        title.text title..attrs year price   .attrs
 1 book  Everyday Italian           en 2005 30.00  COOKING
 2 book      Harry Potter           en 2005 29.99 CHILDREN
 3 book XQuery Kick Start           en 2003 49.99      WEB
 4 book      Learning XML           en 2003 39.95      WEB

Вот как он выглядит с включенным автором. Вы должны использовать ldply в этом случае, так как список «зазубренный»... приложение не может обработать это должным образом. [В противном случае вы можете использовать lapply с rbind.fill (также любезно предоставлено Хэдли), но зачем беспокоиться, когда plyr автоматически делает это для вас?]:

ldply(xmlToList(books), data.frame)

   .id        title.text title..attrs              author year price   .attrs
1 book  Everyday Italian           en Giada De Laurentiis 2005 30.00  COOKING
2 book      Harry Potter           en        J K. Rowling 2005 29.99 CHILDREN
3 book XQuery Kick Start           en      James McGovern 2003 49.99      WEB
4 book      Learning XML           en         Erik T. Ray 2003 39.95      WEB
     author.1   author.2   author.3               author.4
1        <NA>       <NA>       <NA>                   <NA>
2        <NA>       <NA>       <NA>                   <NA>
3 Per Bothner Kurt Cagle James Linn Vaidyanathan Nagarajan
4        <NA>       <NA>       <NA>                   <NA>
-121--2518637-

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

-121--1918204-

Ваши тесты слишком сложны.

Вы должны проверить аспекты вашего класса, а не писать unittest для каждого члена вашего класса. Неразборчивость не должна охватывать всю функциональность члена.

0
ответ дан 7 December 2019 в 01:21
поделиться

Если вы уже включили что-либо , вы можете использовать load для повторного включения файла. Любые определения в файле load перезаписывают существующие методы.

-121--2349481-

Я не думаю, что вы делаете что-то неправильно. Локальная ветвь не зависит от существования удаленной ветви, поэтому удаленная ветвь должна быть удалена, в то время как локальная ветвь (с тем же именем и, по-видимому, той же историей) должна продолжать существовать. Это «централизованный» подход, который вы описываете, если что, который будет считаться неортодоксальным в Git.

Edit: Tangential, вас могут заинтересовать опции --track и -no-track для git-branch, а также переменная конфигурации branch.autosetupmerge .

-121--4028792-

Две мысли: сначала вы должны иметь интеграционный тест в любом случае, чтобы убедиться, что все висит вместе. Во-вторых, мне кажется, что вы пропускаете промежуточные объекты. В моем мире 50 строк - это длинный метод. Трудно сказать что-то более точное, не видя кода.

2
ответ дан 7 December 2019 в 01:21
поделиться

Целочисленные типы:

short            -> signed short
signed short
unsigned short
int              -> signed int
signed int
unsigned int
signed           -> signed int
unsigned         -> unsigned int
long             -> signed long
signed long
unsigned long

Будьте внимательны к символу:

char  (is signed or unsigned depending on the implmentation)
signed char
unsigned char
-121--815680-

ядро Linux может быть записано в C. Оно все еще компилируется для машинного кода. И именно этот машинный код выполняется во время загрузки

Вы также можете написать программное обеспечение, которое выполняется во время загрузки. Таким образом, можно создать собственную пользовательскую ОС или собственное пользовательское программное обеспечение, которое может работать без ОС напрямую. Однако остерегайтесь, что ОС дает вам много функциональных возможностей, которые вам придется сделать себе. Поддержку драйверов, процедуры ввода-вывода дисков, сетевые стеки, многозадачность и управление памятью необходимо выполнять самостоятельно.

Наконец: Я не думаю, что людям это не так нравится, если им нужно перезагрузить свой компьютер, чтобы запустить ваше программное обеспечение. Так что я бы пошел писать для ОС... это облегчает жизнь для вас и пользователя.

-121--3035677-

Я предполагаю, что каждый тест для Run () устанавливает ожидания для каждого метода, который они вызывают для насмешек, даже если этот тест не фокусируется на проверке каждого такого вызова метода. Я настоятельно рекомендую вам Google «издевательства не заглушки» и читать статью Фаулера.

Кроме того, 50 строки кода довольно сложны. Сколько кодепатов через метод? 20+? Вы можете извлечь выгоду из более высокого уровня абстракции. Мне нужно увидеть код, чтобы судить более точно.

0
ответ дан 7 December 2019 в 01:21
поделиться

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

После того, как вы можете получить ваши предварительные условия для метода. Это в основном, в основном тип данных каждого параметра и ограничения и ограничений на каждом из этих параметров (и в любой другой переменной, инициализированной в начале метода).

Таким образом, вы можете быть уверены, что вход и выход - это то, что желательно.

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

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

1
ответ дан 7 December 2019 в 01:21
поделиться

О том, будет ли тестировать такую ​​функцию: вы сказали в комментарии

«Тесты прочитаны так же, как фактический функция, и так как я использую издевательства, это только утверждение функций называется и отправляются парам (я могу проверить Это глазок 50 линий Функция) "

imho eyeballing Функция недостаточно, вы не слышали:« Я не могу поверить, что я пропустил это! »... У вас есть честное количество сценариев, которые могут ошибаться в этом методе Покрытие этой логики является хорошей идеей.

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

На тестах слишком долго / трудно узнать, что находится в том, что там: не тестируйте односмысленные сценарии с каждым единственным утверждением, что это связано с Это. Разбивайте его, тестируйте такие, как он должен регистрировать X сообщений, когда Y получится (1 тест), он должен сохранить до БД, когда y получится (еще один отдельный тест), он должен отправить запрос на третью сторону, когда Z возникает ( Еще один тест) и т. Д.

при выполнении тестов интеграции / системы вместо этих модульных тестов: Вы можете увидеть из вашей текущей ситуации Что существует множество сценариев и небольших вариантов, связанных с этой частью вашей системы. Это с щитом замены еще больше логики с этими издевательствами и простотой моделирования разных условий. Делать то же самое во всем, что добавит целый новый уровень сложности к вашему сценарию, то, наверное, безупречно, если вы хотите покрыть широкий набор сценариев.

ИМХО, вы должны свести к минимуму комбинации, которые вы уходите на ваши системные тесты, выполняя некоторые основные сценарии, должны уже сказать вам, что многие системы работают правильно - это должно быть многое обо всем, что нужно правильно.

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

1
ответ дан 7 December 2019 в 01:21
поделиться

Итак, я должен даже беспокоить это Функция высокого уровня?

Да. Если есть разные кодовые пути, вы должны быть.

А что я делаю, делаю это? Или я полностью не буду с использованием издевательства / заглушки Объекты здесь?

как j.b. Указал (приятно видеть вас в Agileindia2010!), Статья Fowler рекомендуется прочитать. Как валовая упрощение: используйте заглушки, когда вам все равно, если вы не заботитесь о значениях, возвращаемых сотрудниками. Если вы возвращающие значения от Collaborator.call_method () изменяют поведение (или вам нужны нетривиальные проверки args, вычисления для возвратных значений), вам нужны издевалки.

Предлагаемые рефакторины:

  1. Попробуйте переместить создание и впрыск издеваний в общее метод настройки. Большинство структур тестирования единиц поддерживают это; Будут вызвать перед каждым тестом
  2. Ваши вызовы logmessage - это маяки - вызов еще раз, чтобы намеренные раскрывать методы. например Submitbarrequest (). Это сократит ваш код производства.
  3. Попробуйте переместить каждое ожидаемое .blah1 (..). Это сократит ваш тестовый код и делает его очень читаемым и легче изменить. например Заменить все экземпляры Отказ

    ждет .once.on (Mockdao) _ .Method ("beginjob") _ .With (новый объект () {permatedby, clientid, Rundate, "Отправлено для BARING"}) _ .Will ([return] .value (0));

с

engivebeginjobondao_andreturnuzero (); // Вы можете назвать его лучше

1
ответ дан 7 December 2019 в 01:21
поделиться

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

1
ответ дан 7 December 2019 в 01:21
поделиться
Другие вопросы по тегам:

Похожие вопросы: