Чи повинен я перевірити свої підкласи або свій абстрактний батьківський клас?


12

У мене є скелетна реалізація, як у пункті 18 від Ефективна Java ( тут розширена дискусія ). Це абстрактний клас, який пропонує 2 відкритих методу methodA () та methodB (), які викликають методи підкласів для "заповнення прогалин", які я не можу визначити абстраговано.

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

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

Моя проблема полягає в тому, що коли я створюю нову реалізацію, я знову пишу тести:

  • Чи підклас називає заданий необхідний метод?
  • Чи має даний метод дану анотацію?
  • Чи отримую я очікувані результати від методу А?
  • Чи отримую я очікувані результати від методу B?

Хоча бачите переваги при такому підході:

  • Він документує вимоги до підкласу за допомогою тестів
  • Він може швидко вийти з ладу в разі проблемного рефакторингу
  • Тестуйте підклас у цілому та за допомогою їх публічного API ("чи працює методA ()?", А не "чи працює цей захищений метод?")

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

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

Чи поширене це питання під час тестування ієрархії класів? Як я можу цього уникнути?

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

ps2: Я гуглив і знайшов багато питань з цього питання. Один з них - це той, хто має чудову відповідь від Найджела Торна, мій випадок - його "номер 1". Це було б чудово, але в даний момент я не можу рефактор, тому мені доведеться жити з цією проблемою. Якби я міг рефактор, я мав би кожен підклас як стратегію. Це було б добре, але я все-таки перевіряв би "основний клас" і тестував стратегії, але я не помітив би жодного рефакторингу, який би порушив інтеграцію між основним класом і стратегіями.

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


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

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

4
@KilianFoth Ви впевнені в цій раді? Це звучить як рецепт крихких тестових одиниць, дуже чутливий до змін дизайну і тому не дуже ретельний.
Конрад Моравський

Відповіді:


5

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

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


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

4

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

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

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

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