Що робити, коли тести TDD виявляють нову функціональність, яка потрібна, а також потрібні тести?


13

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

Наприклад, я пишу точковий клас, який має функцію WillCollideWith ( LineSegment ) :

public class Point {
    // Point data and constructor ...

    public bool CollidesWithLine(LineSegment lineSegment) {
        Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
                                               Position.Y + Velocity.Y);
        LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
        if (lineSegment.Intersects(pointPath)) return true;
        return false;
    }
}

Я писав тест на CollidesWithLine, коли зрозумів, що мені знадобиться функція LineSegment.Intersects ( LineSegment ) . Але я повинен просто зупинити те, що роблю на своєму тестовому циклі, щоб створити цю нову функціональність? Це, здається, порушує принцип "Червоний, Зелений, Рефактор".

Повинен чи я просто написати код , який виявляє , що lineSegments Intersect всередині CollidesWithLine функції і реорганізувати це після того, як вона працює? Це спрацювало б у цьому випадку, оскільки я можу отримати доступ до даних із LineSegment , але як бути у випадках, коли такі дані є приватними?

Відповіді:


14

Просто прокоментуйте свій тестовий та останній код (або поставте у сховище), щоб ви насправді повернули годинник до початку циклу. Потім почніть з LineSegment.Intersects(LineSegment)тесту / коду / рефактора. Після цього скасуйте свій попередній тест / код (або витягніть з сховища) і продовжуйте цикл.


Чим це відрізняється від того, щоб просто ігнорувати його та повертатися до нього пізніше?
Джошуа Харріс

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

Що таке приховування? це як контроль над версіями?
Джошуа Гарріс

1
деякі VCS реалізують це як особливість (принаймні, Git та Fossil). Це дозволяє видалити зміну, але зберегти її для повторного застосування через деякий час. Робити це вручну не важко, просто збережіть diff та поверніться до останнього стану. Пізніше ви знову застосуєте різницю та продовжуєте продовжувати.
Хав'єр

6

На циклі TDD:

На фазі "зробити тестовий пропуск" ви повинні написати найпростішу реалізацію, яка зробить тестовий пропуск . Щоб зробити свій тестовий пропуск, ви вирішили створити нового співавтора для обробки відсутньої логіки, оскільки для вашого тестового проходу, можливо, було занадто багато роботи, щоб зробити свій тестовий пропуск. Ось тут і криється проблема. Я гадаю, тест, який ви зв'язали пройти, був надто великим кроком . Тому я думаю, що проблема полягає в самому вашому тесті, ви повинні видалити / прокоментувати цей тест і розробити більш простий тест, який дозволить вам зробити крок дитини, не вводячи частину LineSegment.Intersects (LineSegment). Один з вас, який пройшов тест, потім може зробити рефакторваш код (Тут ви застосуєте принцип SRP), перемістивши цю нову логіку в метод LineSegment.Intersects (LineSegment). Ваші тести все одно пройдуть, тому що ви не змінили жодної поведінки, а просто перемістили якийсь код.

На вашому поточному дизайнерському рішенні

Але для мене у вас тут є більш глибока проблема дизайну - ви порушуєте Принцип єдиної відповідальності . Роль пункту - це… бути точкою, ось і все. Немає розумності бути точкою, це просто і значення x і y. Бали - це типові значення . Це однакові речі для сегментів, сегменти - це типи значень, що складаються з двох точок. Вони можуть містити трохи «розумності», наприклад, щоб обчислити їх довжину, виходячи з їх положення в точках. Але це все.

Тепер вирішити, чи стикаються точка і відрізок, це ціла відповідальність сама по собі. І, звичайно, занадто багато роботи, щоб точка або сегмент впоралися самостійно. Він не може належати до класу Point, тому що в іншому випадку бали будуть знати про сегменти. І він не може належати до сегментів, оскільки сегменти вже нести відповідальність за догляд за точками в сегменті і, можливо, також обчислюють довжину самого сегмента.

Отже, ця відповідальність повинна бути власником іншого класу, наприклад, наприклад, "PointSegmentCollisionDetector", який би мав такий метод, як:

bool AreInCollision (Точка p, сегмент s)

І це те, що ви випробували окремо з Точок та Сегментів.

Приємно, що з цією конструкцією тепер ви можете мати різну реалізацію свого детектора зіткнень. Тому було б легко, наприклад, орієнтувати свій ігровий движок (я припускаю, що ви пишете гру: p), перемикаючи метод виявлення зіткнень під час виконання. Або зробити деякі візуальні перевірки / експерименти під час виконання між різними стратегіями виявлення зіткнень.

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

Сподіваюся, це має сенс,


Ви маєте рацію, що я намагався перевірити занадто велику зміну, і я вважаю, що ви маєте рацію розділити це на клас зіткнення, але це змушує мене задати абсолютно новий питання, з яким ви могли б мені допомогти: Чи варто мені використовувати інтерфейс, коли методи лише подібні? .
Джошуа Харріс

2

Найпростіше зробити TDD спосіб - це витягнути інтерфейс для LineSegment та змінити параметр методу для прийняття в інтерфейс. Тоді ви можете знущатися над сегментом рядка введення та кодом / перевірити метод Intersect самостійно.


1
Я знаю, що це метод TDD, який я чую найбільше, але ILineSegment не має сенсу. Одна справа - інтерфейсувати зовнішній ресурс або щось, що може бути в багатьох формах, але я не бачу жодної причини, коли б я приєднав будь-яку функціональність до будь-якого іншого, ніж сегмент лінії.
Джошуа Харріс

0

За допомогою jUnit4 ви можете використовувати @Ignoreпримітку для тестів, які ви хочете відкласти.

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

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