Чи слід тестувати значення перерахунків, використовуючи одиничні тести?


15

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

Я думав, що вони повинні бути написані, навіть якщо вони можуть здатися простими та зайвими, я вважаю, що те, що стосується специфікації бізнесу, повинно бути чітко написано в тесті, будь то з одиницею / інтеграцією / ui / тощо. тести або з використанням типової системи мови як методу тестування. Оскільки значення, які повинен мати enum (наприклад, у Java), з точки зору бізнесу, неможливо перевірити, використовуючи систему типів, я думаю, що для цього повинен бути одиничний тест.

Це питання не схоже на це, оскільки воно не стосується тієї самої проблеми, що і моя. У цьому питанні є функція бізнесу (savePeople), і людина запитує про внутрішню реалізацію (forEach). Там є середній бізнес-шар (функція врятувати людей), що інкапсулює мовну конструкцію (forEach). Тут мовна конструкція (enum) є тією, яка використовується для конкретизації поведінки з точки зору бізнесу.

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


19
Як саме виглядатиме одиничний тест перерахунку?
jonrsharpe


@jonrsharpe Стверджується, що значення, які знаходяться всередині перерахунку, - це ті, від яких ви очікуєте. Я б це зробив, повторивши значення перерахунків, додавши їх до набору, наприклад у вигляді рядків. Замовте цей набір. Порівняйте цей показник із упорядкованим списком значень, написаним від руки в тесті. Вони повинні відповідати.
IS1_SO

1
@jonrsharpe, я люблю вважати одиничні тести також як "визначення" або "вимоги", написані в коді. Одиничний тест перерахунку був би таким же простим, як перевірка кількості елементів на перерахунок та їх значень. Особливо в C #, де перерахунки не є класами, але їх можна безпосередньо відобразити на цілі числа, гарантуючи їх значення, а не програмування за збігом обставин може виявитися корисним для цілей серіалізації.
Мачадо

2
IMHO це не набагато корисніше, ніж тестування, якщо 2 + 2 = 4, щоб перевірити, чи правильно Всесвіт. Ви перевіряєте код за допомогою цього enum, а не самого enum.
Agent_L

Відповіді:


39

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

Ні, вони просто державні.

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

Перевірка перелічень на повноту є аналогічною тестуванню, що всі представлені цілі числа є.

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


5
Але в цьому випадку деталізація реалізації збігається з "справжньою природою" даних, тобто: набором (в математичному сенсі) значень. Ви можете, безперечно, використовувати незмінний набір, але ті самі значення все одно повинні бути присутніми там. Якщо ви використовуєте масив, тестування бізнес-логіки потрібно зробити те ж саме. Я думаю, що тут головною проблемою є той факт, що мовна конструкція дуже добре збігається з природою даних. Я не впевнений, чи правильно я пояснив себе.
IS1_SO

4
@ IS1_SO - на думку VOU, що один тест повинен бути невдалим: чи не так? У такому випадку вам не потрібно було тестувати Enum спеціально. Хіба ні? Може бути , це знак того, що ви могли б змоделювати ваш код більш просто і створити абстракцію над «істинної природою» дані - наприклад , незалежно від карт в колоді, ви дійсно повинні мати уявлення [ Hearts, Spades, Diamonds, Clubs] якщо ви коли-небудь отримуєте картку, якщо карта червона / чорна?
черговий день

1
@ IS1_SO дозволяє сказати, що у вас є перелік кодів помилок, і ви хочете викинути null_ptrпомилку. Тепер це код помилки через enum. Перевірка коду на null_ptrпомилку шукає код і через enum. Тож воно може мати значення 5(для колишнього). Тепер вам потрібно додати ще один код помилки. Перерахунок змінено (скажемо, що ми додаємо новий у верхню частину переліку) Значення null_ptrзараз 6. Це проблема? тепер ви повертаєте код помилки 6та перевіряєте на 6. Поки все логічно послідовно, ви добре, незважаючи на те, що ця зміна порушила ваш теоретичний тест.
Балдрік

17

Ви не перевіряєте декларацію перерахунку . Ви можете перевірити, чи мають вхід / вихід функції очікувані значення перерахунку. Приклад:

enum Parity {
    Even,
    Odd
}

Parity GetParity(int x) { ... }

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

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

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


Я оновив своє запитання, намагаючись вирішити цю відповідь, перевірте, чи допомагає це.
IS1_SO

@ IS1_SO: Добре, що мене бентежить - ти динамічно генеруєш значення перерахунків, або що відбувається?
ЖакБ

Ні. Що я мав на увазі, що в цьому випадку мовна конструкція, обрана для представлення значень, є перерахунком. Але, як ми знаємо, це деталь реалізації. Що станеться, якщо для вибору значень вибирається масив або набір <> (у Java) або рядок з деякими лексемами розділення? Якщо це так, то було б доцільно перевірити, що містяться цінності представляють інтерес для бізнесу. Це моя думка. Чи допомагає це пояснення?
IS1_SO

3
@ IS1_SO: Ви говорите про тестування екземпляра, який повертається з функції, має певне очікуване значення? Тому що так, ви можете це перевірити. Вам просто не потрібно перевіряти декларацію перерахунку.
ЖакБ

11

Якщо є ризик, що зміна перерахунку порушить ваш код, тоді впевнено, що завгодно з атрибутом [Flags] в C # було б гарним випадком, тому що додавання значення між 2 і 4 (3) було би побіжно 1 і 2, а не a стриманий предмет.

Це шар захисту.

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

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


5
Якщо числові значення перерахунку використовуються де-небудь, наприклад, коли вони зберігаються в базі даних, то переупорядкування (включаючи видалення або вставлення до останнього значення) може призвести до недійсності існуючих записів.
stannius

3
+1, ця відповідь занижена. Якщо ваші перерахунки або є частиною серіалізації, вхідного інтерфейсу із зовнішньою інформацією про слово або побітовою інформацією, їх обов'язково потрібно перевірити на відповідність у кожній версії системи. Принаймні, якщо ви переживаєте про зворотну сумісність, що зазвичай є хорошою справою.
Мачадо

11

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

Зважаючи на це, ви повинні перевірити поведінку, яка залежить від значень перерахунків. Наприклад, якщо ви використовуєте значення enum для серіалізації об'єктів до json або будь-якого іншого, або зберігаєте значення в базі даних, ви повинні перевірити поведінку для всіх значень enum. Таким чином, якщо перерахунок модифікований, принаймні один з тестів повинен бути невдалим. У будь-якому випадку те, що ви б тестували, - це поведінка навколо вашого переліку, а не сама декларація enum.


3

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

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


1

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

Колись у мене був випадок, що один модуль використовував перелічувальні типи з двох інших модулів і відображав між ними. (Один з переліків мав із собою додаткову логіку, інший - для доступу до БД; обидва мали залежності, які слід ізолювати один від одного.)

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

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


1

Енуми - це просто кінцеві типи, що мають власні (сподіваюся, значимі) назви. Перерахунок може мати лише одне значення, voidяке містить лише nullодне (деякі мови називають це unit, і використовують ім'я voidдля enum без елементів!). Він може мати два значення, наприклад, boolяке має falseі true. Це може бути три, як colourChannelз red, greenі blue. І так далі.

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

Наприклад, resultщо містить win/ lose/ drawізоморфно вище colourChannel, так як ми можемо замінити , наприклад , colourChannelз result, redз win, greenз loseі blueз draw, і до тих пір , як ми робимо це всюди (виробники і споживачі, парсери і serialisers, записи бази даних, файли журналів і т.д. ) тоді змін у нашій програмі не буде. Будь-які " colourChannelтести", які ми писали, все-таки пройдуть, хоча більше немає colourChannel!

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

Це означає, що що стосується машини, то перерахунки - це "розрізнені назви" та більше нічого . Єдине, що ми можемо зробити із перерахунком - це розгалуження, чи є два значення однаковими (наприклад, red/ red) чи різними (наприклад, red/ blue). Тож це єдине, що може зробити "одиничний тест", наприклад

(  red == red  ) || throw TestFailure;
(green == green) || throw TestFailure;
( blue == blue ) || throw TestFailure;
(  red != green) || throw TestFailure;
(  red != blue ) || throw TestFailure;
...

Як говорить @ jesm00, такий тест - це перевірка мовної реалізації, а не вашої програми. Ці тести ніколи не є хорошою ідеєю: навіть якщо ви не довіряєте мовній реалізації, ви повинні перевірити її ззовні , оскільки не можна довіряти правильному виконанню тестів!

Так це теорія; як щодо практики? Основна проблема при цій характеристиці переліків полягає в тому, що програми "реального світу" рідко є автономними: у нас є застарілі версії, віддалені / вбудовані розгортання, історичні дані, резервні копії, живі бази даних тощо, тому ми ніколи не можемо реально "вимкнути" всі зустрічі імені без пропуску деяких застосувань.

Але такі речі не є "відповідальністю" самого перерахунку: зміна перерахунку може порушити спілкування з віддаленою системою, але, навпаки, ми можемо вирішити таку проблему, змінивши перерахунок!

У таких сценаріях перерахунок є червоношкірим оселедцем: що робити, якщо одна система потребує цього таким чином, а інша - це саме так? Це не може бути обом, незалежно від того, скільки тестів ми пишемо! Справжнім винуватцем в цьому є інтерфейс введення / виводу, який повинен створювати / споживати чітко визначені формати, а не "будь-яке ціле число, яке вибирає інтерпретатор". Таким чином, справжнє рішення полягає в тестуванні інтерфейсів вводу-виводу : з тестовими одиницями, щоб перевірити, чи він розбирає / друкує очікуваний формат, і тестами інтеграції, щоб перевірити, чи формат насправді прийнятий іншою стороною.

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

  • Покриття коду може нам сказати, чи достатньо різноманітності значень перерахувань, що надходять із тестового набору, для запуску різних гілок коду. Якщо ні, ми можемо додати тести, які запускають незакриті гілки, або генерують більшу кількість переліків у існуючих тестах.
  • Перевірка властивості може сказати нам, чи достатньо різноманітності гілок у коді, щоб обробити можливості виконання. Наприклад, якщо код обробляє лише redми, а ми лише тестуємо red, ми маємо 100% охоплення. Перевірка властивості (спробує) генерує контрприклади до наших тверджень, таких як генерування значень greenта blueзначень, які ми забули перевірити.
  • Мутаційне тестування може нам сказати, чи насправді наші твердження перевіряють перелік, а не просто слідкувати за гілками та ігнорувати їхні відмінності.

1

Ні. Тести одиниць призначені для тестових одиниць.

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

https://en.wikipedia.org/wiki/Unit_testing

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


0

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

Вам не потрібно чітко стверджувати, що тип enum має записи, які ви очікуєте, подібно до того, як ви не заявляєте прямо про те, що клас існує насправді або що він має очікувані методи та атрибути.

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

Зауважте, що вам не потрібні значущі імена, щоб правильно зробити ваш код, це просто зручність для людей, які читають ваш код. Ви можете змусити ваш код працювати зі значеннями enum, наприклад foo, bar... та такими методами frobnicate().

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