Як виправити помилку в тесті, після написання виконання


21

Який найкращий спосіб дії в TDD, якщо після правильної реалізації логіки тест все-таки не працює (бо в тесті є помилка)?

Наприклад, припустимо, ви хочете розробити таку функцію:

int add(int a, int b) {
    return a + b;
}

Припустимо, ми розробляємо його наступними кроками:

  1. Написати тест (поки функцій немає):

    // test1
    Assert.assertEquals(5, add(2, 3));
    

    Результати помилки компіляції.

  2. Напишіть реалізацію фіктивної функції:

    int add(int a, int b) {
        return 5;
    }
    

    Результат: test1паси.

  3. Додати ще один тестовий випадок:

    // test2 -- notice the wrong expected value (should be 11)!
    Assert.assertEquals(12, add(5, 6));
    

    Результат: test2не вдається, test1все-таки проходить.

  4. Написати реальну реалізацію:

    int add(int a, int b) {
        return a + b;
    }
    

    Результат: test1все-таки проходить, test2все ще не вдається (з тих пір 11 != 12)

У цьому конкретному випадку: чи було б краще:

  1. виправити test2і побачити, що це зараз проходить, або
  2. видаліть нову частину реалізації (тобто поверніться до кроку №2 вище), виправте test2і нехай це не вдасться, а потім знову введіть правильну реалізацію (крок №4. вище).

Або є якийсь інший, розумніший спосіб?

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

EDIT (У відповідь на відповідь @Thomas Junk):

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


3
Рефакторинг проти червоної смуги є актуальною концепцією.
RubberDuck

5
Зрозуміло, що вам потрібно робити TDD на своєму TDD.
Blrfl

17
Якщо хтось коли-небудь запитає мене, чому я скептично ставлюся до TDD, я вкажу їх на це питання. Це Кафкеск.
Traubenfuchs

@Blrfl ось що нам каже Xibit »Я поставив TDD в TDD, щоб ви могли TDD під час TDDing«: D
Thomas Junk

3
@Traubenfuchs Я визнаю, що питання здається дурним на перший погляд, і я не є прихильником "робити TDD весь час", але я вважаю, що є сильна користь, щоб тест не вдався, а потім написати код, який робить тест пройти (про що насправді йдеться у цьому питанні).
Вінсент Савард

Відповіді:


31

Абсолютно критично те, що ви бачите тест як прохідним, так і невдалим.

Чи ви видалите код, щоб зробити тест невдалим, тоді перепишіть код або проскочіть його до буфера обміну, щоб лише потім вставити його назад, не має значення. TDD ніколи не говорив, що вам потрібно нічого вводити. Він хоче знати, що тест проходить лише тоді, коли він повинен пройти, а провалюється лише тоді, коли він повинен пройти.

Бачити тест як прохідним, так і невдалим - це тест на тест. Ніколи не довіряйте тесту, якого ви ніколи не бачили, щоб робити те і інше.


Рефакторинг проти червоної смуги дає нам формальні кроки для відновлення робочого тесту:

  • Виконати тест
    • Зверніть увагу на зелену смугу
    • Розбийте тестований код
  • Виконати тест
    • Зверніть увагу на червону смужку
    • Рефактор тесту
  • Виконати тест
    • Зверніть увагу на червону смужку
    • Скасуйте тестований код
  • Виконати тест
    • Зверніть увагу на зелену смугу

Однак ми не перетворюємо робочий тест. Ми повинні перетворити тест на баггі. Одна з проблем - код, який було введено, поки лише цей тест охоплював його. Такий код слід повернути назад і знову ввести, як тільки виправлено тест.

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

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

Введіть зелений тест

  • Виконати тест
    • Зверніть увагу на зелену смугу
    • Розбийте тестований код
  • Виконати тест
    • Зверніть увагу на червону смужку
    • Скасуйте тестований код
  • Виконати тест
    • Зверніть увагу на зелену смугу

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

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

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

Мій завдяки Rubberduck для Охоплюючи Червоної Bar посилання.


2
Найкраще мені подобається ця відповідь: Важливо бачити тест з ладом з неправильним кодом, тому я видалив / прокоментував би код, виправляв тести і побачив їх невдачу, повернув код (можливо, введіть навмисну ​​помилку, щоб поставити тести на тест) і виправити код, щоб він працював. Це дуже XP, щоб повністю видалити та переписати його, але іноді просто потрібно бути прагматичним. ;)
GolezTrol

@GolezTrol Я думаю, що моя відповідь говорить те саме, тому я вдячний за будь-які відгуки, які ви мали про те, чи це було незрозуміло.
jonrsharpe

@jonrsharpe Ваша відповідь теж хороша, і я її висловив перед тим, як прочитати навіть цю. Але там, де ви дуже суворі у відношенні коду, CandiedOrange пропонує більш прагматичний підхід, який більше мені подобається.
GolezTrol

@GolezTrol Я не сказав, як відновити код; коментуйте це, виріжте та вставте, зберігайте його, використовуйте історію IDE; це насправді не має значення. Найважливішим є те, чому ви це робите: щоб ви могли перевірити, що ви отримуєте правильний збій. Я відредагував, сподіваюся уточнити.
jonrsharpe

10

Яку загальну мету ви хочете досягти?

  • Робити приємні тести?

  • Створення правильної реалізації?

  • Чи править ТТД релігійно правильно ?

  • Жодні з вищезазначених?

Можливо, ви переосмислите своє відношення до тестів і до тестування.

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

Беручи свій приклад:

"Правильна" реалізація додатку була б еквівалентом коду a+b. І поки ваш код це робить , ви б сказали, що алгоритм правильний у тому, що він робить, і правильно реалізований.

int add(int a, int b) {
    return a + b;
}

На перший погляд , ми обидва погодилися б, що це є реалізація доповнення.

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

Переповнення цілого числа відбувається в коді, але не в концепції addition. Отже: ваш код певним чином поводиться як концепція addition, але це не так addition.

Ця досить філософська точка зору має кілька наслідків.

І одне, можна сказати, тести - це не що інше, як припущення очікуваної поведінки вашого коду. При тестуванні коду, ви могли б (можливо) ніколи не переконаєтеся , що ваша реалізація є право , то краще , що ви могли б сказати, що ваші очікування щодо того , що результати ваш код поставляє були або не були задоволені; будь то, що ваш код невірний, будь то, що ваш тест невірний чи не так, що обидва вони помиляються.

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

Це не допомагає, якщо ви зробили неправильні припущення; але ей! принаймні, це запобігає шизофренії: очікувати різних результатів, коли їх не повинно бути.


тл; д-р

Який найкращий спосіб дії в TDD, якщо після правильної реалізації логіки тест все-таки не працює (бо в тесті є помилка)?

Ваші тести - це припущення про поведінку коду. Якщо у вас є вагомі підстави вважати, що ваша реалізація правильна, виправте тест і перевірте, чи відповідає це припущення.


1
Я вважаю, що питання щодо загальних цілей є досить важливим, дякую, що ви його вирішили. Для мене найвищим пріоритетом є: 1. правильна реалізація 2. "приємні" тести (або, я б сказала швидше, "корисні" / "добре розроблені" тести). Я розглядаю TDD як можливий інструмент для досягнення цих двох цілей. Отже, хоча я не хочу обов'язково релігійно слідувати TDD, в контексті цього питання мене найбільше цікавить перспектива TDD. Я відредагую питання, щоб уточнити це.
Attilio

Отже, ви б написали тест, який тестує на переповнення та проходить, коли це відбувається, або ви зробите його невдалим, коли це відбувається, оскільки алгоритм додавання і переповнення дає неправильну відповідь?
Джеррі Єремія

1
@JerryJeremiah Моя думка: те, що повинні охоплювати ваші тести, залежить від вашого випадку використання. У випадку використання, коли ви додаєте купу однозначних цифр, алгоритм досить хороший . Якщо ви знаєте, що дуже ймовірно, що ви складете "великі цифри", datatypeце явно неправильний вибір. Тест дозволить виявити, що: ваші сподівання будуть "працювати для великої кількості", а в кількох випадках не виправдані. Тоді питання полягало б у тому, як поводитися з цими справами. Це кутові шафи? Коли так, як з ними боротися? Можливо, деякі пропозиції щодо карт допоможуть запобігти більший безлад. Відповідь пов'язана з контекстом.
Томас Джунк

7

Вам потрібно знати, що тест не вдасться, якщо реалізація неправильна, що не те саме, що проходження, якщо реалізація правильна. Тому вам слід повернути код у стан, коли ви очікуєте , що він вийде з ладу, перш ніж виправити тест, і переконайтеся, що він не працює з тієї причини, яку ви очікували (тобто 5 != 12), а не щось інше, чого ви не передбачили.


Як ми можемо перевірити, що тест не відповідає з тієї причини, яку ми очікуємо?
Василевс

2
@Basilevs: Ви зробите гіпотезу щодо того, якою має бути причина невдачі; 2. запустити тест; і 3. прочитайте отримане повідомлення про помилку та порівняйте. Іноді це також підказує способи, як ви можете переписати тест, щоб отримати більш значущу помилку (наприклад, assertTrue(5 == add(2, 3))дає менш корисний результат, ніж assertEqual(5, add(2, 3))навіть якщо вони обидва тестують одне і те ж).
jonrsharpe

Досі незрозуміло, як застосувати цей принцип тут. У мене є гіпотеза - тест повертає постійне значення, як би повторний тест знову підтвердив би, що я правий? Очевидно, щоб перевірити це, мені потрібен ІНШИЙ тест. Я пропоную додати явний приклад для відповіді.
Василевс

1
@Basilevs що? Вашою гіпотезою на кроці 3 було б "тест не вдається, оскільки 5 не дорівнює 12" . Запустивши тест, ви покажете, чи не виходить з цього приводу тест, в якому випадку ви продовжуєте роботу, або з якоїсь іншої причини, і в цьому випадку ви з'ясуєте чому. Можливо, це мовна проблема, але мені незрозуміло, що ти пропонуєш.
jonrsharpe

5

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

Однак те ж саме питання може виникнути і в більш складних ситуаціях, наприклад, коли у вашому коді налаштування є помилка. У такому випадку, після виправлення тесту, ви, ймовірно, повинні спробувати мутувати свою реалізацію таким чином, щоб той тест вийшов з ладу, а потім відновив мутацію. Якщо повернення реалізації - це найпростіший спосіб зробити це, то це добре. У вашому прикладі ви можете мутувати a + bдо a + aабо a * b.

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


0

Я б сказав, це стосується вашої улюбленої системи контролю версій:

  1. Поетапно виправляйте тест, зберігаючи зміни коду у вашому робочому каталозі.
    Зверніться до відповідного повідомлення Fixed test ... to expect correct output.

    З git, це може зажадати використання, git add -pякщо тест і реалізація знаходяться в одному файлі, інакше ви можете, очевидно, просто встановити два файли окремо.

  2. Введіть код реалізації.

  3. Поверніться назад, щоб перевірити скоєння, зроблене на кроці 1, переконавшись, що тест насправді не працює .

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

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