Подтвердить что ты не робот

Использование TDD: "сверху вниз" и "снизу вверх"

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

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

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

Если кто-нибудь может дать мне несколько указателей, это будет очень признательно.

4b9b3361

Ответ 1

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

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

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

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

[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());
    }
}

Вы можете посмотреть полный пост здесь.

Ответ 4

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


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


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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

#include "something"

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

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

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