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


14

Я є тестовим керуванням методом, який полягає у формуванні колекції об'єктів даних. Хочу переконатися, що властивості об’єктів встановлені правильно. Деякі властивості будуть встановлені на одне і те ж; для інших буде встановлено значення, яке залежить від їх положення в колекції. Природний спосіб зробити це, здається, за допомогою петлі. Однак Рой Ошерово настійно рекомендує не використовувати логіку в одиничних тестах ( Art of Unit Testing , 178). Він каже:

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

Як правило, тести повинні представляти собою низку викликів методів, без потоків управління, навіть try-catchі з викликами затвердження.

Однак я не можу бачити нічого поганого в моєму дизайні (як ще ви створюєте список об'єктів даних, деякі значення яких залежать від того, в якій послідовності вони перебувають? - не можна точно генерувати та перевіряти їх окремо). Чи є щось, що не відповідає тесту в моєму дизайні? Або я занадто жорстко відданий вченню Ошерова? Або є якась секретна тестова одинична магія, про яку я не знаю, що обходить цю проблему? (Я пишу на C # / VS2010 / NUnit, але шукаю мовно-агностичні відповіді, якщо це можливо.)


4
Рекомендую не петляти. Якщо ваш тест полягає в тому, що для третьої речі встановлена ​​смужка "Frob", тоді напишіть тест, щоб спеціально перевірити, чи є смужка третьої речі Frob. Це один тест сам по собі, переходьте прямо до нього, ніякої петлі. Якщо ваш тест полягає в тому, що ви отримуєте колекцію з 5 речей, це теж один тест. Це не означає, що ви ніколи не маєте циклу (явного чи іншого), це просто не потрібно часто . Крім того, ставитесь до книги Ошерова як до більшої настанови, ніж до фактичних правил.
Ентоні Пеграм

1
@AnthonyPegram Набори не упорядковані - Frob іноді може бути 3-м, іноді - 2-м. Ви не можете покластися на нього, зробивши цикл (або мовну функцію на зразок Python in) необхідною, якщо тест "Frob був успішно доданий до існуючої колекції".
Ізката

1
@Izbata, його питання конкретно зазначає, що впорядкування важливе. Його слова: "іншим буде встановлено значення, яке залежить від їхньої позиції в колекції". У C # (мові, на яку він посилається) існує багато типів колекцій, які впорядковані для вставки. З цього питання ви також можете покластися на замовлення зі списками на Python, мовою, яку ви згадуєте.
Ентоні Пеграм

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

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

Відповіді:


16

TL; DR:

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

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

Напишіть тест, який потрібно написати.

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

Я також вкажу на шматочок потворного тесту:

Коли код некрасивий, тести можуть бути некрасивими.

Ви не любите писати потворні тести, але негативний код потребує тестування найбільше.

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

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


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

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


7

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

Я сказав у оригінальному запитанні

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

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

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

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

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


4

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

  1. Якщо колекція фіксованої довжини, напишіть одиничний тест, щоб перевірити довжину. Якщо вона має змінну довжину, напишіть кілька тестів на тривалість, які характеризуватимуть її поведінку (наприклад, 0, 1, 3, 10). У будь-якому випадку не перевіряйте властивості в цих тестах.
  2. Напишіть одиничний тест, щоб перевірити кожен із властивостей. Якщо колекція має фіксовану довжину і коротку, просто стверджуйте проти одного властивості кожного з елементів для кожного тесту. Якщо він фіксованої довжини, але довгий, виберіть репрезентативний, але невеликий зразок елементів, який потрібно стверджувати проти кожного властивості. Якщо він має змінну довжину, створіть відносно коротку, але репрезентативну колекцію (тобто, можливо, три елементи) та затвердіть проти одного властивості кожного.

Якщо зробити це таким чином, тести роблять достатньо маленькими, що виведення циклів не здається болючим. Приклад C # / одиниця, заданий метод, що тестується ICollection<Foo> generateFoos(uint numberOfFoos):

[Test]
void generate_zero_foos_returns_empty_list() { ... }
void generate_one_foo_returns_list_of_one_foo() { ... }
void generate_three_foos_returns_list_of_three_foos() { ... }
void generated_foos_have_sequential_ID()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("ID1", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID2", foos.Current.id);
    foos.MoveNext();
    Assert.AreEqual("ID3", foos.Current.id);
}
void generated_foos_have_bar()
{
    var foos = generateFoos(3).GetEnumerable();
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
    foos.MoveNext();
    Assert.AreEqual("baz", foos.Current.bar);
}

Якщо ви звикли до парадигми «тестування плоскої одиниці» (без вкладених структур / логіки), ці тести здаються досить чистими. Таким чином, у тестах уникне логіки шляхом ідентифікації вихідної проблеми як спроби перевірити занадто багато властивостей одразу, а не бракувати циклів.


1
Ошерово мав би твою голову на блюді за те, що він мав три твердження. ;) Перший збій означає, що ви ніколи не підтверджуєте решту. Зауважте також, що ви не дуже уникали циклу. Ви просто явно розширили його у виконаному вигляді. Не важка критика, а просто пропозиція скористатись більшою практикою, ізолюючи свої тестові випадки до мінімально можливої ​​суми, щоб дати собі більш конкретний зворотній зв'язок, коли щось не вдалося, продовжуючи перевіряти інші випадки, які, можливо, все ще можуть пройти (або провалити, з власні специфічні відгуки).
Ентоні Пеграм

3
@AnthonyPegram Я знаю про парадигму "одне утвердження за тест". Я вважаю за краще мантру "тестувати одне" (як виступає Боб Мартін, проти "твердження за тест" в " Чистому кодексі" ). Бічна примітка: рамки тестування одиниць, які мають "очікування" порівняно з "стверджувати", є приємними (Google Tests). Щодо решти, чому б ви не розділили свої пропозиції на повну відповідь із прикладами? Я думаю, що я міг би отримати вигоду.
Казарк
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.