Вам слід розбити розглянутий клас.
Кожен клас повинен виконати кілька простих завдань. Якщо ваше завдання занадто складне для тестування, то завдання, яке виконує клас, занадто велике.
Ігноруючи тугість цієї конструкції:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
ОНОВЛЕННЯ
Проблема методів заглушки в класі полягає в тому, що ви порушуєте інкапсуляцію. Ваш тест повинен перевіряти, чи відповідає зовнішня поведінка об'єкта специфікації. Що б не відбувалося всередині об'єкта, це не є його ділом.
Те, що FullName використовує FirstName та LastName, є детальною частиною реалізації. Нічого поза класом не повинно піклуватися про те, щоб це було правдою. Знущаючись над загальнодоступними методами з метою перевірки об'єкта, ви робите припущення про те, що цей об'єкт реалізований.
В якийсь момент майбутнього це припущення може перестати бути правильним. Можливо, вся логіка імен буде перенесена на об'єкт Name, який Особа просто викликає. Можливо, FullName отримає прямий доступ до змінних учасників first_name та прізвище, а не викликає FirstName та LastName.
Друге питання - чому ви відчуваєте потребу в цьому. Зрештою, ваш клас людини може бути перевірений на кшталт:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
Ви не повинні відчувати необхідності заглушити що-небудь для цього прикладу. Якщо ви це зробите, то ви задушені і добре ... припиніть! Немає користі знущатися над методами, оскільки ти все одно маєш контроль над тим, що є в них.
Єдиний випадок, коли, мабуть, має сенс знущатися з методів, використовуваних FullName, - якщо якось FirstName () та LastName () були нетривіальними операціями. Можливо, ви пишете один із цих генераторів випадкових імен, або FirstName та LastName запитуєте базу даних для відповіді, чи щось. Але якщо це відбувається, то це дозволяє припустити, що об'єкт робить щось, що не належить до класу Person.
Іншим способом, глузування з методів - це взяття предмета і розбиття на дві частини. Один шматок знущається, а інший випробовується. Те, що ви робите, - це, по суті, спеціальний розпад об'єкта. Якщо це так, просто розбийте об’єкт вже.
Якщо ваш клас простий, вам не слід відчувати необхідності знущатися над його частинами під час тесту. Якщо ваш клас досить складний, що ви відчуваєте потребу знущатися, то вам слід розбити клас на більш прості елементи.
ОНОВЛЕННЯ ПРОТИ
Як я це бачу, об’єкт має зовнішню і внутрішню поведінку. Зовнішня поведінка включає виклики, що повертають значення в інші об'єкти тощо. Очевидно, що все в цій категорії повинно бути протестовано (інакше що б ви протестували?) Але внутрішню поведінку насправді не слід перевіряти.
Тепер тестується внутрішня поведінка, адже саме це призводить до зовнішньої поведінки. Але я не пишу тести безпосередньо на внутрішню поведінку, лише опосередковано через зовнішню поведінку.
Якщо я хочу щось перевірити, я вважаю, що це слід перенести так, щоб він став зовнішньою поведінкою. Ось чому я думаю, що якщо ви хочете знущатися над чим-небудь, вам слід розділити об'єкт так, що те, що ви хочете знущатися, тепер знаходиться у зовнішній поведінці об'єктів, про які йдеться.
Але яка різниця це має? Якщо FirstName () та LastName () є членами іншого об’єкта, чи дійсно це змінить проблему FullName ()? Якщо ми вирішимо, що потрібно знущатися над FirstName та LastName, чи справді це допомога їм опинитися на іншому об’єкті?
Я думаю, якщо використовувати свій глузливий підхід, то створюєш шов в об’єкті. У вас є такі функції, як FirstName () та LastName (), які безпосередньо спілкуються із зовнішнім джерелом даних. Ви також маєте FullName (), який не робить. Але оскільки всі вони в одному класі, це не видно. Деякі фрагменти не мають прямого доступу до джерела даних, а інші є. Ваш код стане чіткішим, якщо просто розбити ці дві групи.
EDIT
Давайте зробимо крок назад і запитаємо: чому ми знущаємось над об’єктами, коли тестуємо?
- Зробіть тести послідовними (уникайте доступу до речей, які змінюються від запуску до запуску)
- Уникайте доступу до дорогих ресурсів (не потрапляйте на сторонні послуги тощо)
- Спростіть тестувану систему
- Спростіть тестування всіх можливих сценаріїв (наприклад, таких як моделювання відмови тощо)
- Уникайте залежно від деталей інших фрагментів коду, щоб зміни в цих інших фрагментах коду не порушили цей тест.
Тепер я думаю, що причини 1-4 не стосуються цього сценарію. Знущання над зовнішнім джерелом під час тестування повного імені вирішує всі ці причини глузування. Єдина деталь, яка не обробляється, це простота, але, здається, об’єкт є досить простим, що не викликає особливих проблем.
Я думаю, що ваше занепокоєння є причиною №5. Занепокоєння полягає в тому, що в якийсь момент зміна впровадження FirstName та LastName порушить тест. Надалі FirstName та LastName можуть отримати імена з іншого місця розташування чи джерела. Але FullName, мабуть, завжди буде FirstName() + " " + LastName()
. Ось чому ви хочете протестувати FullName, глузуючи з FirstName та LastName.
Тоді у вас є деякий підмножина об'єкта людини, який, швидше за все, зміниться, ніж інші. Решта об’єкта використовує цей підмножина. Цей підмножина наразі отримує свої дані за допомогою одного джерела, але пізніше може отримати ці дані зовсім іншим чином. Але мені здається, що підмножина - це виразний об'єкт, який намагається вийти.
Мені здається, що якщо ви знущаєтесь над методом об’єкта, ви розділяєте об'єкт. Але ви робите це тимчасово. Ваш код не дає зрозуміти, що всередині вашого об'єкта Person є дві чіткі фігури. Тому просто розділіть цей об’єкт у фактичному коді, щоб з читання вашого коду було зрозуміло, що відбувається. Виберіть фактичний розкол об’єкта, який має сенс, і не намагайтеся розділити об'єкт по-різному для кожного тесту.
Я підозрюю, що ви можете заперечити, щоб розколоти ваш об’єкт, але чому?
EDIT
Я був неправий.
Ви повинні розділити об'єкти, а не вводити спеціальні розщеплення, глузуючи з окремих методів. Однак я був надто зосереджений на одному методі розбиття об’єктів. Однак OO забезпечує безліч способів розщеплення об'єкта.
Що я пропоную:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Можливо, саме цим ви займалися весь час. Але я не думаю, що цей метод не матиме проблем, які я бачив із глузуючими методами, оскільки ми чітко окреслили, на якій стороні кожен метод. І, використовуючи успадкування, ми уникаємо незручностей, які виникли б, якби ми використали додатковий об’єкт обгортки.
Це вводить певну складність, і я маю на увазі лише пару корисних функцій, які, можливо, просто перевірять їх, глузуючи з основного стороннього джерела. Звичайно, у них підвищена небезпека зламу, але переставляти його не варто. Якщо у вас є достатньо складний об'єкт, який вам потрібно розділити, то я думаю, що щось подібне - це гарна ідея.