Чи варто перевірити всі наші методи?


62

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

SomeClass getSomething(parameters) {
    return myDao.findSomethingBySomething(parameters);
}

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

Це змусило мене подумати. Чи слід прагнути до найвищого рівня тестового покриття? Або це просто мистецтво заради мистецтва тоді? Я просто не бачу жодних причин для тестування таких речей, як:

  • геттери і сетери (якщо вони насправді не мають певної логіки)
  • код "котельня"

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

Чи є якісь раціональні / не "вогненебезпечні" причини, чому слід перевіряти кожен (або стільки, скільки він може) рядків коду?


2
Я все ще вирішую це питання, але ось розмова того, хто вирішив відповідь "ні". Ian Cooper: TDD, куди все пішло не так Щоб узагальнити цю чудову розмову, вам слід перевірити зовнішню програму та перевірити нову поведінку, а не нові методи.
Даніель Каплан

Це справді чудова розмова, що треба бачити, розмовляти очей для багатьох людей, я люблю це. Але я думаю, що відповідь його не "ні". Це "так, але опосередковано". Ян Купер розповідає про шестикутну архітектуру та тестові особливості / поведінки, що глузують / заглушують порти. У цьому випадку ці порти є DAO, і цей "менеджер / служба" перевіряється не індивідуальним тестом одиниці лише для цього класу, а "тестовою одиницею" (одиниця в визначенні Ian Cooper, з якою я повністю згоден), яка перевіряє деяку особливість у вашому домені, які використовують цей менеджер / послугу.
AlfredoCasado


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

Відповіді:


49

Я йду за правилом Кента Бека:

Перевірте все, що могло зламатись.

Звичайно, це є певною мірою суб'єктивним. Мені, тривіальних геттерів / сеттерів та однокласників, як ваш вище, зазвичай не варто. Але знову ж таки, я витрачаю більшу частину свого часу, пишучи одиничні тести на спадковий код, лише мрію про приємний проект GreenD TDD ... На таких проектах правила різні. Завдяки старовинному коду головна мета полягає в тому, щоб якомога менше зусиль охопити якомога більше ґрунту, тому одиничні тести мають тенденцію бути вищим рівнем і складнішими, більше схожими на тести інтеграції, якщо хтось педантичний щодо термінології. І коли ви намагаєтеся отримати загальний охоплення коду від 0%, або просто встигли зіткнути його понад 25%, найменше ваших турбот - це тестування одиниць тестування та сетерів.

OTOH у проекті TDD на greenfield, може бути більш реальним написати тести навіть для таких методів. Тим більше, що ви вже написали тест, перш ніж ви отримаєте шанс почати задаватися питанням: "чи варто один цей рядок присвячений тесту?". І принаймні ці тести є тривіальними для написання та швидкістю запуску, так що це не є великою справою.


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

5
@Zenzen: "Що тут може зламатися? Не дуже багато". - Так може зламатися. Просто невеликий друкарський помилок. Або хтось додає якийсь код. Або псує залежність. Я дійсно думаю, що Бек стверджує, що ваш головний приклад кваліфікується як зламаний. Геттери та сетери, тим більше, хоча я вже тоді потрапив у помилку копіювання / вставки. Справжнє питання полягає в тому, що якщо занадто банально написати тест, чому він взагалі існує?
pdr

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

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

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

13

Існує кілька типів тестування одиниць:

  • На базі держави. Ви дієте, а потім стверджуєте проти стану об'єкта. Наприклад, я роблю депозит. Потім я перевіряю, чи збільшився баланс.
  • На основі вартості повернення Ви дієте і стверджуєте проти поверненої вартості.
  • На основі взаємодії Ви перевіряєте, що ваш об’єкт назвав іншим об'єктом. Це здається, що ви робите у своєму прикладі.

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

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

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

Наразі логіки там немає, але це не завжди так.

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

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

Сказавши все це - я не став би сліпо перевіряти все. Я б встановив гарячі точки (компоненти з високою складністю та високим ризиком зламу). Тоді я б зосередився на цих компонентах. Немає сенсу мати кодову базу, де 90% кодової бази є досить простою, і вона охоплена одиничними тестами, коли решта 10% представляють основну логіку системи, і вони не покриваються одиничними тестами через їх складність.

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


Навіщо ви перевіряли, що ваш DAL був викликаний лише один раз?
Мар'ян Венема

9

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

  1. перевірка типу - це вирішення частини проблеми.
  2. тестування впорається з рештою

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


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

6

На мою думку, цикломатична складність є параметром. Якщо метод недостатньо складний (наприклад, геттери і сетери). Не потрібно тестування одиниць. Рівень цикломатичної складності МакКейба повинен бути більше 1. Інше слово має бути мінімум 1 блок твердження.


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

3

Звуковий ДА з TDD (і за кількома винятками)

Суперечливо добре, але я стверджую, що кожен, хто відповість "ні" на це питання, не вистачає фундаментальної концепції TDD.

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

DDD в TDD

TDD часто цитується як у вас основні переваги.

  • Оборона
    • Забезпечення коду може змінитися, але не його поведінка .
    • Це дозволяє настільки важливу практику рефакторингу .
    • Ви отримуєте цей TDD чи ні.
  • Дизайн
    • Ви вказуєте, що щось має робити, як воно має вести себе, перш ніж реалізувати це.
    • Це часто означає більш обізнані рішення щодо впровадження .
  • Документація
    • Набір тестів повинен слугувати специфікацією (вимогами) документації.
    • Використання тестів з цією метою означає, що документація та реалізація завжди знаходяться в узгодженому стані - зміна одного означає зміну іншому. Порівняйте з вимогами дотримання та оформленням на окремому документі слова.

Окрему відповідальність від впровадження

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

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

Набагато важливіше сказати, що об’єкт повинен:

Дозвольте своїм клієнтам змінити стан

і

Дозвольте своїм клієнтам запитувати його стан

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

Тест, такий як

(The Painter class) should store the provided colour

важливо для документаційної частини TDD.

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

Відсутність інженерної розвідки ...

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

1 Броді, Майкл Л. "Джон Мілопулос: пошиття насіння концептуального моделювання". Концептуальне моделювання: основи та програми. Спрингер Берлін Гейдельберг, 2009. 1-9.

... і як TDD це вирішує

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

Створіть спочатку, реалізуйте пізніше

У TDD ми спочатку пишемо невдалий тест на прийняття, лише потім пишемо код, який дозволяє їм пройти.

У BDD вищого рівня ми спочатку пишемо сценарії, а потім пропускаємо їх.

Чому слід виключати сетери та геттери?

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

Тож запитайте себе:

Якщо особа, яка пише тести для класу, повинна згадати про геттерів та сетер.

Оскільки getters та setters є загальнодоступним інтерфейсом для класу, відповідь, очевидно, так , інакше не буде можливості встановити або запитати стан об'єкта.

Очевидно, що якщо ви пишете код спочатку, відповідь може бути не такою чіткою.

Винятки

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

Наприклад, локальний метод 'B ()':

function A() {

    // B() will be called here    

    function B() {
        ...
    }
} 

Або приватна функція square()тут:

class Something {
private:
    square() {...}
public:
    addAndSquare() {...}
    substractAndSquare() {...}
}

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


1

Зіткнувшись з філософським питанням, поверніться до вимог водіння.

Ваша мета виробляти досить надійне програмне забезпечення за конкурентними цінами?

Або це виробляти програмне забезпечення максимально можливої ​​надійності майже незалежно від вартості?

До певного моменту, дві цілі якості та швидкості розвитку / вартості вирівнювання: ви витрачаєте менше часу на написання тестів, ніж на виправлення дефектів.

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

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

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


0

Ваша відповідь на це залежить від вашої філософії (вірите, що це Чикаго проти Лондона? Я впевнений, що хтось це погляне). Журі все ще йде на це з найбільш ефективним часом підходу (адже, адже це найбільший рушій цього часу - менше часу, витраченого на виправлення).

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


0

Це складне питання.

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

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

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

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


-1

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

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

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

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


-4

Це змусило мене подумати. Чи слід прагнути до найвищого рівня тестового покриття?

Так, в ідеалі на 100%, але деякі речі не піддаються контролю.

геттери і сетери (якщо вони насправді не мають певної логіки)

Геттери / сетери дурні - просто не використовуйте їх. Замість цього розмістіть змінну свого члена у загальнодоступному розділі.

код "котельня"

Вийміть загальний код і випробуйте його. Це повинно бути таким же простим.

Чи є якісь раціональні / не "вогненебезпечні" причини, чому слід перевіряти кожен (або стільки, скільки він може) рядків коду?

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

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


Ну давайте зрозуміти одне: я не мав на увазі, що не використовую тести TDD / запис. Зовсім навпаки. Я знаю, що в тестах може виявитись помилка, про яку я не думав, але що тут перевірити? Я просто думаю, що такий метод є одним із "не перевірених одиниць". Як сказав Петер Трьок (цитуючи Кента Бека), ви повинні перевірити речі, які можуть зламатися. Що тут може зламатися? Не дуже багато (у цьому методі є лише проста делегація). Я МОЖУ написати тестовий блок, але це буде просто макет DAO і затвердження, не дуже тестування. Щодо геттерів / сеттерів, деякі рамки вимагають їх.
Дзенцен

1
Крім того, оскільки я цього не помічав: "Вийміть загальний код і випробовуйте його. Це повинно бути таким же простим". Що ти маєш на увазі? Це клас обслуговування (у рівні обслуговування між графічним інтерфейсом та DAO), загальний для всього додатка. Неможливо зробити його більш загальним (оскільки він приймає деякі параметри та викликає певний метод у DAO). Єдина причина, що вона полягає в тому, щоб дотримуватися шаруватої архітектури програми, щоб GUI не викликав DAO безпосередньо.
Дзенцен

20
-1 для "Getters / Setters дурні - просто не використовуйте їх. Замість цього поставте змінну свого члена в загальний розділ." - Дуже неправильно. Це обговорювалося кілька разів на SO . Використання публічних полів скрізь насправді гірше, ніж використання гетерів та сетерів скрізь.
Péter Török
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.