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


17

Це правила Роберта К. Мартіна щодо TDD :

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

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

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

Примітка. Я намагався задати це питання тут: Чи можна почати з проходження одиничного тесту? Але я досі не зміг сформулювати це питання досить добре.


"Катання в боулінг-ката", зв'язана в статті, яку ви цитуєте, насправді є негайним тестом як завершальним кроком.
jscs

Відповіді:


21

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

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

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


Поліпшення тестового покриття існуючого коду - ще одна цілком вагома причина для написання (сподіваємось) проходження тесту.
Джек

13

Це означає, що або:

  1. Ви написали виробничий код, який відповідає необхідній функції, не спершу написавши тест (порушення "релігійної TDD"), або
  2. Функція, яка вам потрібна, вже виконана виробничим кодом, і ви просто пишете ще один тестовий пристрій для покриття цієї функції.

Остання ситуація частіше, ніж можна подумати. Як цілком пристойний і тривіальний (але все-таки показовий) приклад, скажімо, що ви написали наступний одиничний тест (псевдокод, тому що я лінивий):

public void TestAddMethod()
{
    Assert.IsTrue(Add(2,3) == 5);
}

Тому що все, що вам дійсно потрібно, - це результат 2 і 3, що додаються разом.

Вашим методом реалізації буде:

public int add(int x, int y)
{
    return x + y;
}

Але скажімо, зараз мені потрібно додати 4 і 6 разом:

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

Мені не потрібно переписувати свій метод, оскільки він вже охоплює другий випадок.

Тепер скажімо, що я з’ясував, що для моєї функції Add дійсно потрібно повернути число, яке має деяку стелю, скажімо, 100. Я можу написати новий метод, який тестує це:

public void TestAddMethod3()
{
    Assert.IsTrue(Add(100,100) == 100);
}

І це випробування тепер не вдасться. Тепер я повинен переписати свою функцію

public int add(int x, int y)
{
    var a = x + y;
    return a > 100 ? 100 : a;
}

щоб змусити його пройти.

Здоровий глузд диктує, що якщо

public void TestAddMethod2()
{
    Assert.IsTrue(Add(4,6) == 10);
}

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


5
Якщо ви повністю слідкували за прикладами Мартіна (і він не обов'язково пропонує вам це зробити), щоб add(2,3)пройти, ви буквально повернетесь 5. Закріплений. Тоді ви б написали тест, add(4,6)який змусив би вас написати виробничий код, який змушує його пройти, при цьому не порушуючись add(2,3)при цьому. Ви б закінчилися з цим return x + y, але не почали б з цього. В теорії. Звичайно, Мартін (а може, це був хтось інший, я не пам’ятаю) любить надавати такі приклади для навчання, але не сподівається, що ви насправді таким чином пишете такий тривіальний код.
Ентоні Пеграм

1
@tieTYT, як правило, якщо я правильно згадую книгу Мартіна, другого тестового випадку, як правило, буде достатньо, щоб ви написали загальне рішення для простого методу (а насправді ви справді просто змусили б його працювати перший раз). Не потрібно третини.
Ентоні Пеграм

2
@tieTYT, тоді ви продовжуватимете писати тести, поки не зробите. :)
Ентоні Пеграм

4
Є третя можливість, і це суперечить вашому прикладу: ви написали дублікат тесту. Якщо ви слідуєте TDD «релігійно», то новий тест, який проходить, таким чином, завжди є червоним прапором. Після DRY , ви ніколи не повинні писати два тести, які тестують по суті те саме.
congusbongus

1
"Якщо ви повністю слідкували за прикладами Мартіна (і він не обов'язково пропонує вам це зробити), щоб зробити додаток (2,3), ви буквально повернете 5." жорстко "." - це шматочок суворої TDD, який завжди був зі мною, ідея про те, що ви пишете код, який ви знаєте, є неправильним у очікуванні майбутнього тесту, який прийде і підтвердить його. Що робити, якщо майбутній тест чомусь ніколи не буде написаний, а колеги припускають, що "все-тести-зелені" означають "все-код-правильний"?
Джулія Хейвард

2

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

Припустимо, канонічний (?) TDD. Виробничого коду немає, але кілька тестових випадків (це, звичайно, завжди не вдається). Ми додаємо виробничий код для проходження. Потім зупиніться тут, щоб додати ще тестовий випадок помилки. Знову додайте виробничий код, щоб пройти.

Іншими словами, ваш тест може бути своєрідним тестом на функціональність, а не простим тестом на модуль TDD. Це завжди цінний актив для якості товару.

Мені особисто не подобаються такі тоталітарні, нелюдські правила; (


2

Насправді той самий випуск виник у доджо вчора ввечері.

Я зробив це швидке дослідження. Ось що я придумав:

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

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

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

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


0

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

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

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

Перший програміст пише:

assert abs(-88888) == 88888
assert abs(-12345) == 12345
assert abs(-5000) == 5000
assert abs(-32) == 32
assert abs(46) == 46
assert abs(50) == 50
assert abs(5001) == 5001
assert abs(999999) == 999999
...

Другий програміст пише:

assert abs(-1) == 1
assert abs(0) == 0
assert abs(1) == 1

Перша реалізація програміста може призвести до:

def abs(n):
    if n < 0:
        return -n
    elif n > 0:
        return n

Реалізація другого програміста може призвести до:

def abs(n):
    if n < 0:
        return -n
    else:
        return n

Усі тести проходять, але перший програміст не лише написав кілька зайвих тестів (зайве уповільнення циклу їх розвитку), але й не зміг перевірити граничний випадок ( abs(0)).

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


Ну, і другий програміст був явно недбалий до тестів, тому що його колега переосмислив abs(n) = n*nі пройшов.
Ейко

@Eiko Ви абсолютно праві. Написання занадто мало тестів може вкусити вас так само погано. Другий програміст був надто скупий, щонайменше, на тестування abs(-2). Як і у всьому, помірність - це ключ.
мислитель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.