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


141

Ми повинні писати тести для наших геттерів та сетерів чи це надмірно?


Я не думаю, що так. Не слід писати тестовий випадок для геттера / сеттера.
Гаррі Джой

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

@skaffman Які сучасні мови не мають властивостей? Звичайно, такі мови, як Java, вимагають, щоб вони були повноцінними методами, але це не робить логічно відмінним від слова C #.
Клаус Йоргенсен

2
@Claus: Він не сказав властивості, він сказав геттерів та сетерів. У Java ви пишете це вручну, іншими мовами ви отримуєте кращу підтримку.
скафман

Відповіді:


180

Я б сказав, що ні.

@Will сказав, що ви повинні орієнтуватися на 100% покриття коду, але, на мою думку, це небезпечне відволікання. Ви можете писати одиничні тести, які мають 100% охоплення, і все ж тестувати абсолютно нічого.

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

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


7
Прагнення до 100% покриття коду нерозумно. Ви в кінцевому підсумку займаєтесь висвітленням коду, який не є загальнодоступним та / або коду без складності. Такі речі найкраще залишити для автогенерації, але навіть автогенеровані випробування досить безглузді. Краще перенести фокус на важливе тестування, як тести інтеграції.
Клаус Йоргенсен

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

5
Я думаю, що 100% охоплення кодом, коли весь тестовий набір запущений, є хорошою метою. Встановлювачі ресурсів та інший код запускаються в рамках більш великих тестів. Останні шматки, які потрібно прикрити, ймовірно, обробка помилок. І якщо обробка помилок не буде охоплена одиничними тестами, вона ніколи не буде покрита. Ви дійсно хочете відправити товар, який містить код, який ніколи не був запущений?
Андерс Абель

Я би схильний не погодитися з цією порадою. Так, геттери і сетери прості, але їх також дивно легко зіпсувати. Кілька рядків простих тестів, і ви знаєте, що вони працюють і продовжують працювати.
Чарльз

Можливо, ви будете тестувати непотрібні сетери, які ви (або інструмент) створили просто для створення "закругленого" POJO. Можливо, ви, наприклад, використовуєте Gson.fromJson, щоб "надути" POJOS (не потрібні сетери). У цьому випадку мій вибір - видалити невикористані сетери.
Альберто Гаона

36

Рой Ошерово у своїй знаменитій книзі "Мистецтво тестування одиниць" говорить:

Властивості (getters / setters на Java) - хороші приклади коду, який зазвичай не містить логіки та не вимагає тестування. Але стежте: як тільки ви додасте будь-яку перевірку всередині ресурсу, ви хочете переконатися, що логіка перевіряється.


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

1
У важливих ранніх книгах часто є погані поради. Я бачив так багато випадків, коли люди швидко кидають на геттерів та сетерів і роблять помилки, тому що вони ріжуть і вставляють або забувають це. або це-> або щось подібне. Їх легко перевірити і, безумовно, слід випробувати.
Чарльз

35

Звучить ТАК з 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 є загальнодоступним інтерфейсом до класу, відповідь, очевидно, так , інакше не вдасться встановити або запитати стан об'єкта. Однак спосіб зробити це не обов’язково, перевіряючи кожен метод окремо, див. Іншу відповідь для отримання додаткової інформації.

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


2
Ось лише одна сторона монети. Подумайте про речі, які ви могли зробити замість тестування лише заради тестування. Як сказав Кент Бек, вам платять за робочий код, а не за робочі тести.
Георгій Олійников

@GeorgiiOleinikov Ви читали мою іншу відповідь нижче? Це майже відповідає вашому погляду.
Іжакі

21

tl; dr: Так, ви повинні , а з OpenPojo це банально.

  1. Ви повинні зробити певну валідацію у ваших геттерів та сетерів, тому вам слід тестувати це. Наприклад, setMom(Person p)не слід допускати, щоб хтось молодший за себе ставився до матері.

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

  3. Поширена помилка - void setFoo( Object foo ){ foo = foo; }там, де вона повинна бути void setFoo( Object foo ){ this.foo = foo; }. (У першому випадку , fooякий записується на це параметр НЕfoo поле на об'єкті ).

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

  5. В іншому випадку, якщо у вас найосновніші сетери / геттери, то одиничне тестування їх додасть, можливо, максимум 10 хвилин на об'єкт, то яка втрата? Якщо ви додасте поведінку, у вас вже є тест на скелет, і ви отримаєте тест регресії безкоштовно. Якщо ви використовуєте Java, ви не маєте виправдання, оскільки існує OpenPojo . Існує існуючий набір правил, які ви можете ввімкнути, а потім сканувати з ними весь ваш проект, щоб переконатися, що вони послідовно застосовуються у вашому коді.

З їх прикладів :

final PojoValidator pojoValidator = new PojoValidator();

//create rules
pojoValidator.addRule( new NoPublicFieldsRule  () );
pojoValidator.addRule( new NoPrimitivesRule    () );
pojoValidator.addRule( new GetterMustExistRule () );
pojoValidator.addRule( new SetterMustExistRule () );

//create testers
pojoValidator.addTester( new DefaultValuesNullTester () );
pojoValidator.addTester( new SetterTester            () );
pojoValidator.addTester( new GetterTester            () );

//test all the classes
for(  PojoClass  pojoClass :  PojoClassFactory.getPojoClasses( "net.initech.app", new FilterPackageInfo() )  )
    pojoValidator.runValidation( pojoClass );

11
Я знаю, що це вже давно, але: МОЖЕ Ви робити валідацію у ваших геттерів та сетерів? У мене було враження, що setMom повинен робити те, що говорить. Якщо він перевіряє, чи не повинен це бути валідатомAndSetMom? Або ще краще, чи не повинен існувати код перевірки ELSEWHERE, ніж простий об'єкт? Що я тут пропускаю?
ndtreviv

14
Так, ви завжди повинні підтверджувати свої дані. Якщо ні, то чому б просто не використовувати загальнодоступну змінну? Це стає тим самим. Вся перевага від використання сетерів проти змінних полягає в тому, що це дозволяє вам переконатися, що ваш об’єкт ніколи не є недійсним, наприклад, вік є від’ємним числом. Якщо ви цього не зробите в об'єкті, ви будете робити це в іншому місці (наприклад, об'єкт "служби" бога ), і це насправді OOP в цій точці.
Sled

1
Ну, це, мабуть, родзинка мого тижня. Дякую.
Sled

19

Так, але не завжди ізольовано

Дозвольте мені детальніше:

Що таке одиничне випробування?

Від ефективної роботи зі застарілим кодом 1 :

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

Зауважте, що з OOP, де ви знайдете геттерів та сеттерів, одиниця - це клас , не обов'язково індивідуальні методи .

Що таке хороший тест?

Усі вимоги та тести відповідають формі логіки Хоара :

{P} C {Q}

Де:

  • {P}є передумовою ( заданою )
  • C- умова спуску ( коли )
  • {Q}є постумовою ( тоді )

Тоді настає максимум:

Тестова поведінка, а не реалізація

Це означає, що ви не повинні перевіряти, як Cдосягається стан, ви повинні перевірити, що {Q}це результат C.

Якщо мова йде про OOP, Cце клас. Тому не слід перевіряти внутрішні ефекти, а лише зовнішні.

Чому б не випробувати ізолятори та сетери в ізоляції

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

Тож не слід випробовувати геттери та сетери окремо. Це погано:

Describe 'LineItem class'

    Describe 'setVAT()'

        it 'should store the VAT rate'

            lineItem = new LineItem()
            lineItem.setVAT( 0.5 )
            expect( lineItem.vat ).toBe( 0.5 )

Хоча якби setVATвикинути виняток, відповідний тест був би доречним, оскільки зараз є зовнішній ефект.

Як слід перевірити геттерів та сетерів?

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

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

Наприклад:

Describe 'LineItem class'

    Describe 'getGross()'

        it 'should return the net time the VAT'

            lineItem = new LineItem()
            lineItem.setNet( 100 )
            lineItem.setVAT( 0.5 )
            expect( lineItem.getGross() ).toBe( 150 )

Ви можете подумати собі:

Почекайте секунду, ми getGross()тут не тестуємо setVAT().

Але якщо setVAT()несправність, тест повинен все-таки вийти з ладу.

1 Перо, М., 2004. Ефективна робота зі спадковим кодом. Prentice Hall Professional.

2 Martin, RC, 2009. Чистий код: довідник про гнучку майстерність програмного забезпечення. Пірсон освіта.


13

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

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

Існують очевидні причини використання властивостей читання / запису, наприклад, властивості ViewModel, які пов'язані з полями введення тощо.

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

  1. Вам не вистачає тестів, які опосередковано використовують властивості
  2. Властивості не використовуються

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


12

Якщо цикломатична складність геттера та / або сетера становить 1 (яким вони зазвичай є), то відповідь «ні», ви не повинні.

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

PS Не забудьте розмежовувати геттери та сетери навіть у таких мовах, як C #, де властивості можуть здаватися однаковими. Складність задачі може бути вищою, ніж у геттера, і, таким чином, підтвердити одиничний тест.


4
Хоча варто перевірити захисне копіювання на геттерах
Sled

8

Гумористичне, але мудре сприйняття : Шлях Тестівуса

"Написати тест можна сьогодні"

Тестування геттерів / сетерів може бути надмірним, якщо ви досвідчений тестер, і це невеликий проект. Однак якщо ви тільки починаєте вивчати тестування модулів, або ці геттери / сетери можуть містити логіку (як, setMom()наприклад , приклад @ ArtB ), тоді було б непогано написати тести.


1
ваше посилання насправді має вказувати на: Шлях Тестівуса
JJS

2

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

Крім того, якщо ви використовуєте звіт про покриття коду, як ми, одне, що ви можете зробити, - це додати атрибут [ExcludeFromCodeCoverage]. Нашим рішенням було використовувати це для моделей, які просто мають властивості за допомогою геттерів та сетерів, або для самої власності. Таким чином, це не вплине на загальний% покриття коду при запуску звіту про покриття коду, припускаючи, що саме це ви використовуєте для обчислення відсотків покриття коду. Щасливого тестування!


2

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

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

Якщо ви перевіряєте лише відсоток покриття кодом у вашому проекті, то відсоток тестового покриття, наприклад 80%, безглуздо. Ви можете перевірити всі логічні частини і забути деякі важливі частини. У цьому випадку лише 100% означає, що ви перевірили весь свій життєвий код (і весь не логічний код). Як тільки це 99,9%, ви знаєте, що щось забули.

До речі: висвітлення коду - це заключна перевірка, щоб побачити, чи ви повністю (одиницю) протестували клас. Але 100% охоплення кодом не обов’язково означає, що ви фактично перевірили всю функціональність класу. Тож одиничні тести завжди повинні здійснюватися, керуючись логікою класу. Врешті-решт ви запускаєте покриття, щоб побачити, чи забули ви щось. Коли ви зробили це правильно, ви вперше потрапили на 100%.

Ще одне: працюючи у великому банку в Нідерландах, я нещодавно помітив, що Sonar вказала на 100% покриття коду. Однак я знав, що чогось не вистачає. Перевіряючи відсотки покриття коду на файл, він вказав на файл із меншим відсотком. Весь базовий відсоток коду був настільки великим, що в одному файлі відсоток не відображався як 99,9%. Тож ви можете поглянути на це ...


1

Я трохи проаналізував покриття, досягнуте в самому коді JUnit .

Одна категорія непокритого коду "занадто проста для тестування" . Сюди входять прості геттери та сетери, які розробники JUnit не тестують.

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


1

Я б сказав: ТАК Помилки в методах геттера / сетера можуть мовчки прокрастись і викликати деякі потворні помилки.

Я написав ліб, щоб полегшити це та деякі інші тести. Єдине, що ви повинні написати в своїх тестах JUnit, це:

        assertTrue(executor.execute(Example.class, Arrays.asList( new DefensiveCopyingCheck(),
            new EmptyCollectionCheck(), new GetterIsSetterCheck(),
            new HashcodeAndEqualsCheck(), new PublicVariableCheck())));

-> https://github.com/Mixermachine/base-test


0

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

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

Хоча це все ще не буде десь таким поганим, як виявлення проблеми у виробництві.

Це може бути хорошою ідеєю, щоб усі ваші абстрактні класи мали конструктори. Якщо ні, тест на геттера може попередити вас про проблему.

Щодо геттерів та сеттерів примітивів, питання може бути таким: чи я тестую свою програму, чи тестую JVM чи CLR? Взагалі кажучи, JVM не потрібно тестувати.

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