Использование TDD: «сверху вниз» против «снизу вверх» - PullRequest
21 голосов
/ 31 декабря 2010

Поскольку я новичок в TDD, в настоящее время я разрабатываю крошечное консольное приложение на C #, чтобы практиковаться (потому что практика совершенствуется, верно?). Я начал с простого наброска того, как приложение может быть организовано (по классам), и начал разрабатывать все классы предметной области, которые я мог бы идентифицировать, один за другим (конечно, сначала тестирование).

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

Полагаю, у меня не было бы этих проблем, если бы я использовал подход "сверху вниз". Вопрос: как бы я это сделал? Должен ли я начать с тестирования метода Main ()?

Если кто-нибудь может дать мне несколько советов, это будет высоко ценится.

Ответы [ 7 ]

31 голосов
/ 31 декабря 2010

«Сверху вниз» - это , уже использованное в вычислениях для описания техники анализа . Вместо этого я предлагаю использовать термин "снаружи-внутрь".

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

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

Когда мы пишем пользовательский интерфейс, мы делаем его максимально тонким. Пользовательский интерфейс будет использовать другой класс - контроллер, модель представления и т. Д. - для которого мы можем определить API.

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

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

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

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

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

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

2 голосов
/ 31 декабря 2010

Если вы переместили материал из main (), не могли бы вы протестировать эту функцию?

Имеет смысл сделать это, так как вы можете захотеть работать с разными cmd-аргументами и захотитепроверить их.

0 голосов
/ 01 января 2011

Главное должно быть очень просто в конце. Я не знаю, как это выглядит в C #, но в C ++ это должно выглядеть примерно так:

#include "something"

int main( int argc, char *argv[])
{
  return TaskClass::Run( argc, argv );
}

Передайте уже созданные объекты конструкторам классов, но в модульных тестах передайте фиктивные объекты.

Для получения дополнительной информации о TDD, посмотрите эти скринкасты . Они объясняют, как сделать гибкую разработку, но также рассказывают, как сделать TDD, с примерами на c #.

0 голосов
/ 01 января 2011

В журнале MSDN за декабрь была опубликована интересная статья, в которой описывается, "как цикл BDD объединяет традиционный цикл разработки через тестирование (TDD) с тестами на уровне функций, которые обеспечивают реализацию на уровне модулей",Детали могут быть слишком специфичными для технологии, но идеи и схема процесса звучат в соответствии с вашим вопросом.

0 голосов
/ 31 декабря 2010

Я рекомендую "сверху вниз" (я называю это тестированием высокого уровня).Я написал об этом:

http://www.hardcoded.net/articles/high-level-testing.htm

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

0 голосов
/ 31 декабря 2010

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


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


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

0 голосов
/ 31 декабря 2010

Вы также можете протестировать консольное приложение. Я нашел это довольно сложно, посмотрите на этот пример:

[TestMethod]
public void ValidateConsoleOutput()
{
    using (StringWriter sw = new StringWriter())
    {
        Console.SetOut(sw);
        ConsoleUser cu = new ConsoleUser();
        cu.DoWork();
        string expected = string.Format("Ploeh{0}", Environment.NewLine);
        Assert.AreEqual<string>(expected, sw.ToString());
    }
}

Полный текст сообщения можно посмотреть здесь .

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