Труднощі з TDD & Refactoring (або - чому це більш болісно, ​​ніж має бути?)


20

Я хотів навчити себе використовувати підхід TDD, і у мене був проект, над яким я хотів працювати деякий час. Це був не великий проект, тому я вважав, що це буде хорошим кандидатом на TDD. Однак я відчуваю, що щось пішло не так. Наведу приклад:

На високому рівні мій проект є надбудовою для Microsoft OneNote, яка дозволить мені легше відстежувати та керувати проектами. Тепер я також хотів зберегти ділову логіку для цього, щоб якомога більше від'єднатись від OneNote, на випадок, якщо я вирішив створити власне власне сховище та задній кінець дня.

Спочатку я почав з базового тесту на прийняття простих слів, щоб окреслити те, що хотів зробити свою першу особливість. Це виглядає приблизно так (скидаючи його на стислість):

  1. Клацання користувача створюють проект
  2. Типи користувачів у назві проекту
  3. Переконайтеся, що проект створений правильно

Пропускаючи речі інтерфейсу та деяке посередницьке планування, я переходжу до свого першого тесту на одиницю:

[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 потрібен ідентифікатор розділу. Я не усвідомлював цього ще тоді, коли думав на рівні контролера, бо займався лише тестуванням бітів, що стосуються контролера. Однак, аж донизу тут я розумію, що я мушу попросити користувача про місце для зберігання проекту. Тепер мені потрібно додати ідентифікатор місцезнаходження в сховище даних, потім додати його до проекту, потім додати його до контролера та додати його до ВСІХ тестів, які вже написані для всіх цих речей. Це стало дуже нудно дуже швидко, і я не можу не стверджувати, що я би зловив це швидше, якби заздалегідь накреслив дизайн, а не дозволяв його розробляти під час процесу TDD.

Може хтось, будь ласка, пояснить мені, чи я зробив щось не так у цьому процесі? Чи все-таки можна уникнути подібного рефакторингу? Або це поширене? Якщо воно є загальним, чи є якісь способи зробити його більш безболісним?

Дякую всім!


Ви отримаєте дуже проникливі коментарі, якщо опублікуєте цю тему на цьому дискусійному форумі: groups.google.com/forum/#!forum/…, що спеціально для тем TDD.
Чак Крутсінгер

1
Якщо вам потрібно додати щось до всіх своїх тестів, це здається, що ваші тести погано написані. Вам слід перефактурувати свої тести і розглянути можливість використання розумного кріплення.
Дейв Гілєр

Відповіді:


19

Хоча TDD (справедливо) рекламується як спосіб розробити і розробити ваше програмне забезпечення, все-таки добре заздалегідь продумати дизайн та архітектуру. ІМО, "розробити дизайн достроково" - це чесна гра. Однак часто це буде на більш високому рівні, ніж дизайнерські рішення, до яких вам приведуть через TDD.

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

  1. Наскільки це можливо, уникайте деталей щодо впровадження тестів. Це означає лише тестування за допомогою публічних методів, і де це можливо, надайте перевагу державі над верифікацією на основі взаємодії . Іншими словами, якщо ви перевірите результат чогось, а не кроків, щоб дістатися, ваші тести повинні бути менш крихкими.

  2. Мінімізуйте дублювання у вашому тестовому коді, як і у виробничому коді. Цей пост є хорошим посиланням. У вашому прикладі здається, що додавати IDвластивість до конструктора було боляче, оскільки ви викликали конструктора безпосередньо в декількох різних тестах. Натомість спробуйте вилучити створення об'єкта до методу або ініціалізувати його один раз для кожного тесту методом ініціалізації тесту.


Я читав достоїнства, що ґрунтуються на державі проти взаємодії, і розумію це більшість часу. Однак я не бачу, як це можливо в кожному випадку, не виставляючи властивостей ЕКСПЛІКАТНО для тесту. Візьміть мій приклад вище. Я не впевнений, як перевірити, чи справді зберігався сховище даних, не використовуючи твердження для "MustHaveBeenCalled". Щодо пункту 2, ви абсолютно правильні. Я все-таки робив це після всіх змін, але мені просто хотілося переконатися, що мій підхід взагалі відповідає прийнятим практикам TDD. Спасибі!
Ландон

@Landon Є випадки, коли тестування взаємодії є більш підходящим. Наприклад, перевірка того, що було здійснено дзвінок до бази даних або веб-служби. В основному, коли вам потрібно ізолювати свій тест, особливо від зовнішньої служби.
jhewlett

@Landon Я "переконаний класицист", тому я не дуже досвідчений з тестуванням на основі інтелекту ... Але вам не потрібно робити твердження для "MustHaveBeenCalled". Якщо ви тестуєте вставку, ви можете скористатися запитом, щоб перевірити, чи була вона вставлена. PS: Я використовую заглушки через міркування щодо продуктивності при тестуванні всього, крім рівня бази даних.
Хбас

@jhewlett До такого висновку я і дійшов. Спасибі!
Ландон

@Hbas Немає бази даних для запиту. Я погоджуюся, що це був би найпростіший шлях вперед, якби у мене був такий, але я додаю це до ноутбука OneNote. Найкраще, що я можу зробити, - це додати метод Get до мого класу помічників interop, щоб спробувати перетягнути сторінку. Я можу написати тест, щоб це зробити, але я відчував, що я перевіряю дві речі одразу: чи я це врятував? і Чи правильно мій клас помічників отримує сторінки? Хоча, я думаю, в якийсь момент ваші тести, можливо, доведеться покладатися на інший код, перевірений в іншому місці. Спасибі!
Ландон

10

... Я не можу не відчути, як би я швидше зловив би це, якби заздалегідь накреслив дизайн, а не дозволяв його розробляти під час процесу TDD ...

Можливо, може й ні

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

З іншого боку, можливо, якби ви почали з функції високого рівня (SaveProject) замість функції нижчого рівня (CreateProject), ви помітили б, що раніше відсутні параметри.

Потім знову, можливо, у вас цього не було б. Це неповторний експеримент.

Але якщо ви шукаєте урок наступного разу: почніть з верху. І подумай над дизайном стільки, скільки хочеш спочатку.


0

https://frontendmasters.com/courses/angularjs-and-code-testability/ Приблизно з 2:22:00 до кінця (близько 1 години). Вибачте, що відео не безкоштовне, але я не знайшов безкоштовного, який би це так добре пояснював.

На цьому уроці одна з найкращих презентацій написання тестового коду. Це клас AngularJS, але тестова частина полягає в коді Java, в першу чергу тому, що те, про що він говорить, не має нічого спільного з мовою, і все, що стосується написання хорошого тестового коду в першу чергу.

Магія полягає в написанні тестового коду, а не при написанні тестів з кодом. Справа не в написанні коду, який видає себе за користувача.

Він також витрачає деякий час на написання специфікації у вигляді тестових тверджень.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.