Я хотел научить себя использовать подход TDD, и у меня был проект, над которым я давно хотел работать. Это был не большой проект, поэтому я подумал, что это будет хорошим кандидатом на TDD. Однако я чувствую, что что-то пошло не так. Позвольте мне привести пример:
На высоком уровне мой проект является надстройкой для Microsoft OneNote, которая позволит мне более легко отслеживать и управлять проектами. Теперь я также хотел сохранить бизнес-логику для этого максимально изолированной от OneNote на тот случай, если я когда-нибудь решу создать собственное хранилище и серверную часть.
Сначала я начал с простого теста на прием простых слов, чтобы описать, что я хотел сделать от своей первой функции. Это выглядит примерно так (из соображений краткости):
- Пользователь нажимает создать проект
- Типы пользователей в заголовке проекта
- Убедитесь, что проект создан правильно
Пропустив пользовательский интерфейс и промежуточное планирование, я прихожу к своему первому модульному тесту:
[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
var testController = new Controller();
Project newProject = testController(A.Dummy<String>());
Assert.IsNotNull(newProject);
}
Все идет нормально. Красный, зеленый, рефакторинг и т. Д. Хорошо, сейчас нужно реально сохранить вещи. Вырезая некоторые шаги здесь я заканчиваю с этим.
[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
var fakeDataStore = A.Fake<IDataStore>();
var testController = new Controller(fakeDataStore);
String expectedTitle = fixture.Create<String>("Title");
Project newProject = testController(expectedTitle);
Assert.AreEqual(expectedTitle, newProject.Title);
}
Я все еще чувствую себя хорошо в этот момент. У меня пока нет конкретного хранилища данных, но я создал интерфейс так, как ожидал.
Я собираюсь пропустить несколько шагов здесь, потому что этот пост становится достаточно длинным, но я следовал за подобными процессами, и в конечном счете я добираюсь до этого теста для моего хранилища данных:
[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
/* snip init code */
testDataStore.SaveNewProject(A.Dummy<IProject>());
A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}
Это было хорошо, пока я не попытался это реализовать:
public String SaveNewProject(IProject project)
{
Page projectPage = oneNoteInterop.CreatePage(...);
}
И есть проблема именно там, где "...". Теперь я понимаю, что CreatePage требует ID раздела. Я не осознавал этого, когда думал на уровне контроллера, потому что меня интересовало только тестирование битов, относящихся к контроллеру. Тем не менее, теперь я понимаю, что должен попросить пользователя указать место для сохранения проекта. Теперь мне нужно добавить идентификатор местоположения в хранилище данных, затем добавить один в проект, затем добавить один в контроллер и добавить его ко ВСЕМ тестам, которые уже написаны для всех этих вещей. Это очень быстро стало утомительно, и я не могу помочь, но чувствую, что поймал бы это быстрее, если бы набросал дизайн раньше, чем позволил бы его разрабатывать во время процесса TDD.
Может кто-нибудь объяснить мне, если я сделал что-то не так в этом процессе? Есть ли в любом случае такого рода рефакторинг можно избежать? Или это распространено? Если это распространено, есть ли способы сделать его более безболезненным?
Спасибо всем!
источник
Ответы:
Хотя TDD (справедливо) рекламируется как способ разработки и развития вашего программного обеспечения, все же стоит заранее подумать о дизайне и архитектуре. ИМО, «делать наброски дизайна раньше времени» - это честная игра. Однако, зачастую это будет на более высоком уровне, чем проектные решения, к которым вы будете обращаться через TDD.
Также верно и то, что когда все меняется, вам обычно приходится обновлять тесты. Нет способа полностью устранить это, но есть некоторые вещи, которые вы можете сделать, чтобы сделать ваши тесты менее хрупкими и минимизировать боль.
Насколько это возможно, не допускайте подробностей реализации в свои тесты. Это означает, что тестирование выполняется только через общедоступные методы, и, где возможно, предпочтение отдается проверке на основе состояния, а не проверке на основе взаимодействия . Другими словами, если вы тестируете результат чего-либо, а не шаги, которые нужно сделать, ваши тесты должны быть менее хрупкими.
Сведите к минимуму дублирование в своем тестовом коде, так же, как в рабочем коде. Этот пост является хорошей ссылкой. В вашем примере кажется, что было болезненно добавлять
ID
свойство в ваш конструктор, потому что вы вызывали конструктор непосредственно в нескольких различных тестах. Вместо этого попробуйте извлечь создание объекта в метод или инициализировать его один раз для каждого теста в методе инициализации теста.источник
Может быть нет
С одной стороны, TDD работал просто отлично, предоставляя вам автоматические тесты по мере создания функциональности и сразу ломая, когда вам приходилось менять интерфейс.
С другой стороны, возможно, если бы вы начали с высокоуровневой функции (SaveProject) вместо низкоуровневой (CreateProject), вы бы заметили пропущенные параметры раньше.
Опять же, может быть, вы бы не стали. Это неповторимый эксперимент.
Но если вы ищете урок в следующий раз: начните сверху. И подумайте о дизайне столько, сколько хотите в первую очередь.
источник
https://frontendmasters.com/courses/angularjs-and-code-testability/ Примерно с 2:22:00 до конца (около 1 часа). Извините, что видео не бесплатное, но я не нашел бесплатного, который так хорошо это объясняет.
На этом уроке представлена одна из лучших презентаций написания тестируемого кода. Это класс AngularJS, но часть тестирования посвящена Java-коду, в первую очередь потому, что то, о чем он говорит, не имеет ничего общего с языком, и в первую очередь связано с написанием хорошего тестируемого кода.
Волшебство заключается в написании тестируемого кода, а не в написании тестов кода. Дело не в написании кода, который притворяется пользователем.
Он также тратит некоторое время на написание спецификации в форме тестовых утверждений.
источник