Как разработать сложные методы с TDD - PullRequest
5 голосов
/ 25 августа 2009

Несколько недель назад я начал свой первый проект с TDD. До сих пор я читал только одну книгу об этом.

Моя основная задача: как писать тесты для сложных методов / классов. Я написал класс, который вычисляет биномиальное распределение. Таким образом, метод этого класса принимает n, k и p в качестве входных данных и вычисляет соотв. вероятность. (На самом деле, он делает немного больше, поэтому мне пришлось написать его самому, но давайте для простоты рассуждений остановимся на этом описании класса.)

Что я сделал, чтобы протестировать этот метод: скопировать несколько таблиц с разными n, которые я нашел в сети, в свой код, выбрав случайную запись в этой таблице, выполнив соотв. значения для n, k и p в мою функцию, и посмотрел, был ли результат рядом со значением в таблице. Я повторяю это несколько раз для каждой таблицы.

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

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

Ответы [ 7 ]

6 голосов
/ 25 августа 2009

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

Чтобы ответить на ваш вопрос, я думаю, что да, в некоторых случаях это не ситуация «2 минуты, пока вы не доберетесь». В этих случаях я думаю, что тесты могут занять много времени, чтобы стать зелеными. Но большинство ситуаций - это ситуации "2 минуты, пока вы не наберете зеленый". В вашем случае (я не знаю сквот о биномиальном распределении), вы написали, что у вас есть 3 аргумента, n, k и p. Если вы сохраняете постоянными k и p, проще ли реализовать вашу функцию? Если да, вы должны начать с создания тестов, которые всегда имеют константы k и p. Когда ваши тесты пройдут, введите новое значение для k, а затем для p.

3 голосов
/ 25 августа 2009

«У меня сложилось впечатление, что я не должен кодировать дольше, чем несколько минут, пока тест снова не станет зеленым. Вестфаль верен до определенной степени.

Некоторые функции начинаются просто и могут быть просто протестированы и просто закодированы.

Некоторые функции начинаются не просто. Простого трудно достичь. EWD говорит, что простота не ценится, потому что ее так трудно достичь.

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

После того, как вы в конце концов достигнете простоты, вы тоже можете написать книгу, показывающую, насколько она проста.

Пока вы не достигнете простоты, написание вещей займет много времени.

«Было ли плохой идеей случайным образом выбирать числа из таблицы?»

Да. Если у вас есть данные образца, запустите тест по all данным образца. Используйте цикл или что-то еще, и протестируйте все, что вы можете протестировать.

Не выбирайте одну строку - случайным образом или иным образом, выберите все строки.

1 голос
/ 25 августа 2009

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

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

Системный тест, вероятно, следует проводить после того, как вы нарастили свою функциональность с помощью небольших модульных тестов с небольшими прямыми методами.

Даже если методы просто извлекают часть формулы из более длинного метода, вы получаете преимущество читабельности (имя метода должно быть более читабельным, чем часть формулы, которую он заменяет), и если методы являются окончательными, JIT следует встроить их, чтобы не потерять скорость.

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

1 голос
/ 25 августа 2009

Вы должны TDD, используя детские шаги. Попробуйте подумать о тестах, которые потребуют меньше кода для написания. Затем напишите код. Затем напишите еще один тест и т. Д.

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

- РЕДАКТИРОВАТЬ - на основе комментариев

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

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

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

0 голосов
/ 25 августа 2009

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

Да. TDD предписывает короткие промежутки между тестированием и реализацией, но то, что вы используете, все еще далеко от стандартов, которые вы найдете в отрасли. Теперь вы можете рассчитывать на код для расчета того, как он должен, и можете реорганизовывать / расширять код со степенью уверенности, что вы его не нарушаете.

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

0 голосов
/ 25 августа 2009

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

класс А { public doLotsOfStuff () // Вызов doTask1..n private doTask1 () личное doTask2 () личное doTask3 () }

Вам будет очень сложно разрабатывать с TDD, если вы начнете с написания теста для наибольшей функциональности (то есть doLotsOfStuff ()). Разобрав проблему на более управляемые куски и приблизив ее к концу простейшей функциональности, вы также сможете создавать более дискретные тесты (гораздо более полезные, чем тесты, которые проверяют все!). Возможно, ваше потенциальное решение можно переформулировать так:

класс А { public doLotsOfStuff () // Вызов doTask1..n public doTask1 () public doTask2 () public doTask3 () }

Хотя ваши частные методы могут быть деталями реализации, это не повод избегать их тестирования в изоляции. Как и многие другие проблемы, подход «разделяй и властвуй» окажется здесь эффективным. Реальный вопрос заключается в том, какой размер является подходящим для тестирования и обслуживания функциональным блоком? Только вы можете ответить на этот вопрос, исходя из ваших знаний о проблеме и вашего собственного суждения о применении ваших способностей для выполнения задачи.

0 голосов
/ 25 августа 2009

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

Что касается вашего второго вопроса: да, я думаю, что это плохая идея сделать тестовое устройство случайным Почему ты сделал это в первую очередь? Смена прибора меняет тест.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...