Как я могу начать использовать TDD для написания некоторых простых функций?

9

У меня в основном есть суть TDD. Я продал, что это полезно, и у меня есть разумное командование структурой MSTEST. Однако до настоящего времени я не смог перейти к использованию его в качестве основного метода разработки. В основном я использую его как суррогат для написания консольных приложений в качестве тестовых драйверов (мой традиционный подход).

Самым полезным для меня является то, как он поглощает роль регрессионного тестирования.

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

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

Я остановился и решил написать этот вопрос после того, как написал этот код (интересно, смогу ли я на самом деле использовать TDD на этот раз)

Код:

interface ITask
{
    Guid TaskId { get; }
    bool IsComplete { get; }
    bool IsFailed { get; }
    bool IsRunning { get; }
}

interface ITaskContainer
{
    Guid AddTask(ICommand action);
}

interface ICommand
{
    string CommandName { get; }
    Dictionary<string, object> Parameters { get; }
    void Execute();
}
Аарон Анодид
источник
Сначала вы должны были написать тесты, а затем интерфейсы! Вся идея в том, что TDD для вашего API.
1
Как написать тесты для интерфейсов, которые еще не существуют? Они даже не будут компилироваться.
Роберт Харви
5
Это ваш первый провальный тест.
Кори

Ответы:

10

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

Итак, в # 1, скажем, вы хотите создать новую команду (я перехожу к тому, как команда будет работать, так что терпите меня). (Кроме того, я буду немного прагматичным, а не экстремальным TDD)

Новая команда называется MakeMyLunch, поэтому вы сначала создаете тест для ее создания и получаете имя команды:

@Test
public void instantiateMakeMyLunch() {
   ICommand command = new MakeMyLunchCommand();
   assertEquals("makeMyLunch",command.getCommandName());
}

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

Итак, затем вы хотите, чтобы он реализовал execute. Поэтому вы должны спросить: откуда мне знать, что «MakeMyLunch» успешно «сделал мой обед». Какие изменения в системе из-за этой операции? Могу ли я проверить это?

Предположим, что это легко проверить на:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   ICommand command = new MakeMyLunchCommand();
   command.execute();
   assertTrue( Lunch.isReady() );
}

В других случаях это сложнее, и вы действительно хотите проверить обязанности испытуемого (MakeMyLunchCommand). Возможно, ответственность MakeMyLunchCommand заключается во взаимодействии с холодильником и микроволновой печью. Таким образом, чтобы проверить это, вы можете использовать макет Холодильник и макет Микроволновая печь. [два примера макетов фреймворка - Mockito и nMock или посмотрите здесь .]

В этом случае вы должны сделать что-то вроде следующего псевдокода:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   Fridge mockFridge = mock(Fridge);
   Microwave mockMicrowave = mock(Microwave);
   ICommand command = new MakeMyLunchCommand( mockFridge, mockMicrowave );
   command.execute();
   mockFramework.assertCalled( mockFridge.removeFood );
   mockFramework.assertCalled( microwave.turnon );
}

Пурист говорит: проверь ответственность своего класса - его взаимодействия с другими классами (открыла ли команда холодильник и включила ли микроволновая печь?).

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

Найдите правильный баланс, который работает для вашей системы.

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

jayraynet
источник
если бы я предварительно не написал свой интерфейс, какой вопрос привел бы к созданию метода Execute () - некоторые мои первоначальные попытки TDD остановились, когда у меня не было «шага» для стимулирования дополнительной функциональности - я получаю чувство, что есть скрытая проблема курицы / яйца, которую нужно обойти
Аарон Анодид
1
Хороший вопрос! Если единственная команда, которую вы сделали, была «MakeMyLunchCommand», то метод мог начаться с «.makeMyLunch ()». Что было бы хорошо. Затем вы делаете другую команду ("NapCommand.takeNap ()"). Все еще нет проблем с другими методами. Затем вы начинаете использовать его в своей экосистеме, где, вероятно, вы вынуждены обобщаться в интерфейс ICommand. В общем, вы часто откладываете обобщение до последнего ответственного момента, потому что YAGNI [ en.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) В других случаях вы знаете, что доберетесь туда, поэтому начинайте с него.
jayraynet
(Также предполагается, что вы используете современную среду IDE, которая делает рефакторинг таких вещей, как имена методов, тривиальным)
jayraynet,
1
Еще раз спасибо за совет, для меня это своего рода веха, чтобы наконец увидеть все части и как они подходят - и да, быстрый рефакторинг в моем наборе инструментов
Аарон Анодид