Чи призначені інтеграційні тести для повторення всіх одиничних тестів?


36

Скажімо, у мене є функція (написана в Ruby, але повинна бути зрозумілою всім):

def am_I_old_enough?(name = 'filip')
   person = Person::API.new(name)
   if person.male?
      return person.age > 21
   else
      return person.age > 18
   end
end

Під час одиничного тестування я створив би чотири тести, щоб охопити всі сценарії. Кожен буде використовувати знурений Person::APIоб’єкт за допомогою стертих методів male?та age.

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

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


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

9
Тільки чотири тести? У вас є шість граничних віків, які ви повинні тестувати: 17, 18, 19, 20, 21, 22 ...;)
Девід Арно

22
@FilipBartuzi, я припускаю, що метод перевіряє, чи є самець старше 21, наприклад? Як написано в даний час, це не робиться, це правда лише, якщо вони 22+. "Більше 21" англійською мовою означає "21+". Отже, у вашому коді є помилка. Такі помилки фіксуються шляхом тестування граничних значень, тобто 20, 21, 22 для самця, 17,18,19 для самки в цьому випадку. Тож потрібно щонайменше шість тестів.
Девід Арно

6
Не кажучи вже про випадки 0 і -1. Що означає людині 1 рік? Що повинен робити ваш код, якщо ваш API поверне щось безглуздо?
RubberDuck

9
Це було б набагато простіше перевірити, якби ви передали людині об'єкт як параметр.
JeffO

Відповіді:


72

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

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

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

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

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


6
"ми можемо отримати досить впевнену оцінку того, що система робить те, що повинна, не потопаючи в альтернативних сценаріях для тестування". Дякую. Я люблю, коли хтось підходить до автоматизованого тестування з розумом.
jpmc26

1
JB Rainsberger приємно поговорив про випробування та комбінаторний вибух, про який ви пишете в останньому параграфі, який називається "Інтегровані тести - це афера" . Справа не стільки в інтеграційних тестах, але все ще досить цікава.
Барт ван Нієроп

The customer doesn't care about a particular utility function you wrote, they care that their web app is properly secured against access by minors-> Це дуже розумний розум, дякую! Проблема полягає в тому, коли ти робиш проект для себе. Важко розділити свою думку між тим, щоб бути програмістом і бути менеджером продукту в той же момент
Філіп Бартузі

14

Коротка відповідь - «Ні». Більш цікава частина - чому / як може виникнути така ситуація.

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

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

Найбільш очевидною причиною є те, що ваша функція виконує два різні завдання:

  • Пошук на Personоснові їх імені. Для цього потрібно тестування інтеграції, щоб переконатися, що він може знайти Personоб’єкти, які, імовірно, створені / зберігаються в іншому місці.
  • Обчислення того, чи Personдостатньо старий, виходячи з їх статі. Для цього потрібне тестування одиниць, щоб переконатися, що обчислення виконуються так, як очікувалося.

Згрупувавши ці завдання разом в один блок коду, ви не можете запустити одне без іншого. Коли ви хочете перевірити обчислення, ви змушені шукати Person(з реальної бази даних або з заглушки / макету). Коли ви хочете перевірити, що пошук інтегрується з рештою системи, ви змушені також виконати розрахунок за віком. Що нам робити з цим розрахунком? Чи слід ігнорувати його чи перевіряти? Це, здається, є точним загрозою, яку ви описуєте у своєму запитанні.

Якщо ми уявляємо альтернативу, ми можемо мати розрахунок самостійно:

def is_old_enough?(person)
   if person.male?
      return person.age > 21
   else 
      return person.age > 18
   end
end

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

Ми також можемо спокуситись окремо написати завдання пошуку:

def person_from_name(name = 'filip')
   return Person::API.new(name)
end

Однак у цьому випадку функціональність наближена до того, Person::API.newщо я б сказав, що ви повинні використовувати це замість цього (якщо ім'я за замовчуванням необхідне, чи краще його зберігати в іншому місці, як атрибут класу?).

Коли ви пишете інтеграційні тести для Person::API.new(або person_from_name), все, що вам потрібно подбати, - чи повернете ви очікуване Person; про всі розрахунки за віком доглядаються в інших місцях, тому ваші інтеграційні тести можуть їх ігнорувати.


11

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

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

Зазвичай можна виконати наші 1000-тестові одиниці тестів для кожної збірки, а потім наші тести інтеграції на 100 або більше значення після кожного розгортання. Ми можемо не брати кожну збірку до розгортання, але це нормально, оскільки збірка, яку ми беремо для розгортання, буде запущена тестами інтеграції. Як правило, ми хочемо обмежити виконання цих тестів протягом 10 або 15 хвилин, оскільки ми не хочемо затримувати розгортання занадто довго.

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


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