Я прочитав цю публікацію про тестування приватних методів. Зазвичай я їх не тестую, бо завжди думав, що швидше тестувати лише публічні методи, які будуть викликані ззовні об'єкта. Ви перевіряєте приватні методи? Чи повинен я завжди тестувати їх?
Я прочитав цю публікацію про тестування приватних методів. Зазвичай я їх не тестую, бо завжди думав, що швидше тестувати лише публічні методи, які будуть викликані ззовні об'єкта. Ви перевіряєте приватні методи? Чи повинен я завжди тестувати їх?
Відповіді:
Я не підключаю тестові приватні методи. Приватний метод - це деталь реалізації, який слід приховати користувачам класу. Тестування приватних методів порушує інкапсуляцію.
Якщо я виявив, що приватний метод є величезним або складним або достатньо важливим, щоб вимагати власних тестів, я просто перекладаю його в інший клас і оприлюднюю його там ( метод Object ). Тоді я можу легко перевірити раніше приватний, але тепер публічний метод, який зараз живе на власному класі.
Яка мета тестування?
Більшість відповідей поки що говорять про те, що приватні методи - це деталі впровадження, які не мають значення (або, принаймні, не мають значення), поки публічний інтерфейс добре перевірений і працює. Це абсолютно правильно, якщо ваша єдина мета тестування - гарантувати, що відкритий інтерфейс працює .
Особисто моє основне використання для тестів коду - це гарантувати, що майбутні зміни коду не спричинятимуть проблем, а також допомагати моїм зусиллям налагодження, якщо вони є. Я вважаю, що тестування приватних методів так само ретельно, як і публічний інтерфейс (якщо не більше!) Сприяє цій меті.
Поміркуйте: у вас є публічний метод A, який викликає приватний метод B. A і B обидва використовують метод C. Змінено C (можливо, ви, можливо, постачальник), що призведе до того, що A починає провалювати свої тести. Чи не було б корисним також тести на B, навіть якщо він приватний, так що ви знаєте, чи проблема полягає у використанні А в C, у використанні B в C або в обох?
Тестування приватних методів також додає значення у випадках, коли покриття тесту публічним інтерфейсом є неповним. Хоча в цілому ми хочемо уникати такої ситуації, тестування блоку ефективності залежить як від тестів, що знаходять помилки, так і пов'язаних з цим витрат на розробку та обслуговування цих тестів. У деяких випадках переваги 100% тестового покриття можуть бути оцінені недостатніми, щоб гарантувати витрати на ці випробування, створюючи прогалини в покритті тесту на публічному інтерфейсі. У таких випадках добре націлений тест приватного методу може бути дуже ефективним доповненням до кодової бази.
testDoSomething()
testDoSomethingPrivate()
виявляється невдалим, ми не знаємо безпосередньо, де першопричина відмови. Це може бути або в одному, або . Це робить тест менш цінним. . Ось кілька причин для тестування особистого stackoverflow.com/questions/34571 / ... :
Я схильний дотримуватися порад Дейва Томаса та Енді Ханта у книзі « Тестування прагматичних одиниць» :
Взагалі, ви не хочете порушувати жодну інкапсуляцію заради тестування (або, як мама казала, "не виставляйте своїх приватних осіб!"). Більшу частину часу вам слід мати можливість протестувати клас, застосовуючи його публічні методи. Якщо є значна функціональність, яка прихована за приватним або захищеним доступом, це може бути попереджувальним знаком того, що там є інший клас, який бореться за вихід.
Але іноді я не можу зупинити себе на тестуванні приватних методів, тому що це дає мені впевненість у тому, що я будую повністю надійну програму.
Я відчуваю вимушеність перевіряти приватні функції, оскільки я все більше і більше дотримуюся однієї з наших останніх рекомендацій із забезпечення якості в нашому проекті:
Не більше 10 в цикломатичній складності на функцію.
Тепер побічним ефектом застосування цієї політики є те, що багато моїх дуже великих публічних функцій поділяються на багато більш цілеспрямованих, краще названих приватних функцій.
Загальнодоступна функція все ще існує (звичайно), але по суті зводиться до всіх цих приватних "підфункцій"
Це насправді круто, тому що зараз callstack набагато простіше читати (замість помилки у великій функції, у мене є помилка в підфункції з іменем попередніх функцій у callstack, щоб допомогти мені зрозуміти "як я туди потрапив")
Тим НЕ менше, тепер , здається , простіше блок-тест безпосередньо ці приватні функції, і залишити тестування великої суспільної функції свого роду тест «інтеграція» , де а потреба сценарію повинні бути розглянута.
Всього мої 2 копійки.
Так, я перевіряю приватні функції, оскільки, хоча вони перевірені вашими загальнодоступними методами, в TDD (Test Driven Design) добре перевірити найменшу частину програми. Але приватні функції недоступні, коли ви перебуваєте в класі свого тестового блоку. Ось що ми робимо для перевірки наших приватних методів.
Чому у нас є приватні методи?
Приватні функції в основному існують у нашому класі, оскільки ми хочемо створити читабельний код у наших публічних методах. Ми не хочемо, щоб користувач цього класу називав ці методи безпосередньо, але через наші загальнодоступні методи. Також ми не хочемо змінювати їх поведінку під час розширення класу (у випадку захищеного), отже, це приватне.
Коли ми кодуємо, ми використовуємо тестово-керований дизайн (TDD). Це означає, що іноді ми натрапляємо на приватний функціонал і хочемо перевірити. Приватні функції не перевіряються в phpUnit, тому що ми не можемо отримати доступ до них у класі Test (вони є приватними).
Ми думаємо, що ось три рішення:
1. Ви можете протестувати своїх приватних осіб за допомогою своїх публічних методів
Переваги
Недоліки
2. Якщо приватне є таким важливим, то, можливо, саме кодовий код створити для нього новий окремий клас
Переваги
Недоліки
3. Змініть модифікатор доступу на (остаточний) захищений
Переваги
Недоліки
Приклад
class Detective {
public function investigate() {}
private function sleepWithSuspect($suspect) {}
}
Altered version:
class Detective {
public function investigate() {}
final protected function sleepWithSuspect($suspect) {}
}
In Test class:
class Mock_Detective extends Detective {
public test_sleepWithSuspect($suspect)
{
//this is now accessible, but still not overridable!
$this->sleepWithSuspect($suspect);
}
}
Тож наш тестовий блок тепер може викликати test_sleepWithSuspect для перевірки нашої колишньої приватної функції.
Мені не подобається тестувати приватну функціональність з кількох причин. Вони наступні (це основні моменти для людей, які TLDR):
Я поясню кожне з них на конкретному прикладі. Виявляється, 2) і 3) дещо хитромудро пов'язані, тому їх приклад схожий, хоча я вважаю їх окремими причинами, чому не слід перевіряти приватні методи.
Існують випадки, коли тестування приватних методів є доцільним, важливо просто знати про перелічені вище недоліки. Пізніше я детальніше перейду над цим.
Я також зауважую, чому TDD не є вагомим приводом для тестування приватних методів у самому кінці.
Один з найпоширеніших (анти) патернів, який я бачу, - це те, що Майкл Пірс називає класом "Айсберг" (якщо ви не знаєте, хто такий Майкл Пір'я, зайдіть, купіть / прочитайте його книгу "Ефективна робота зі спадщинним кодексом"). людина, про яку варто знати, якщо ви професійний інженер / розробник програмного забезпечення). Є й інші (анти) зразки, які спричиняють вирішення цієї проблеми, але це, безумовно, найпоширеніший, на який я натрапив. Класи «Айсберг» мають один публічний метод, а решта - приватні (саме тому перевірити приватні методи спокусливо). Його називають класом "Айсберг", оскільки зазвичай існує самотня публічна методика, але решта функціональності прихована під водою у вигляді приватних методів.
Наприклад, ви можете перевірити GetNextToken()
, подзвонивши по рядку послідовно і побачивши, що він повертає очікуваний результат. Така функція гарантує тест: така поведінка не є тривіальною, особливо якщо ваші правила токенізації складні. Давайте зробимо вигляд, що це не все так складно, і ми просто хочемо мотузки в жетонах, обмежених простором. Отже, ви пишете тест, можливо, це виглядає приблизно так (якийсь мовний агностичний псуедо-код, сподіваємось, ідея зрозуміла):
TEST_THAT(RuleEvaluator, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
re = RuleEvaluator(input_string);
ASSERT re.GetNextToken() IS "1";
ASSERT re.GetNextToken() IS "2";
ASSERT re.GetNextToken() IS "test";
ASSERT re.GetNextToken() IS "bar";
ASSERT re.HasMoreTokens() IS FALSE;
}
Ну, це насправді виглядає досить приємно. Ми хотіли б переконатися, що ми підтримуємо цю поведінку під час внесення змін. Але GetNextToken()
це приватна функція! Таким чином, ми не можемо перевірити його так, оскільки він навіть не збирається (припускаючи, що ми використовуємо мову, яка фактично застосовує публічну / приватну, на відміну від деяких мов скриптів, таких як Python). Але як щодо зміни RuleEvaluator
класу на дотримання принципу єдиної відповідальності (єдиного принципу відповідальності)? Наприклад, у нас, здається, є аналізатор, токенізатор та оцінювач, застряглі в одному класі. Чи не було б краще просто розділити ці обов'язки? Крім того, якщо ви створюєте Tokenizer
клас, то це публічні методи були б HasMoreTokens()
і GetNextTokens()
. У RuleEvaluator
класі може бути аTokenizer
об’єкт як член. Тепер ми можемо зберегти той же тест, що і вище, за винятком того, що ми перевіряємо Tokenizer
клас замість RuleEvaluator
класу.
Ось як це може виглядати в UML:
Зауважте, що цей новий дизайн збільшує модульність, тому ви могли потенційно повторно використовувати ці класи в інших частинах вашої системи (до цього ви не змогли приватні методи не використовувати повторно за визначенням). Це головна перевага розбиття оцінювача правил, а також підвищення зрозумілості / локальності.
Тест виглядав би надзвичайно схожим, за винятком того, що він би справді компілювався цього разу, оскільки GetNextToken()
метод тепер відкритий для Tokenizer
класу:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS FALSE;
}
Навіть якщо ви не думаєте, що можете розбити свою проблему на меншу кількість модульних компонентів (що ви можете 95% часу, якщо ви просто намагаєтесь це зробити), ви можете просто протестувати приватні функції через публічний інтерфейс. Багато разів приватні учасники не варто тестувати, оскільки вони будуть перевірені через загальнодоступний інтерфейс. Я багато разів бачу тести, які дуже схожі, але перевіряють дві різні функції / методи. У кінцевому підсумку відбувається те, що коли вимоги змінюються (і вони завжди є), тепер у вас є 2 розбиті тести замість 1. І якщо ви дійсно перевірили всі ваші приватні методи, у вас може бути більше, як 10 зламаних тестів замість 1. Коротше , тестування приватних функцій (за допомогоюFRIEND_TEST
або оприлюднення їх або використання рефлексії), яке в іншому випадку можна перевірити через загальнодоступний інтерфейс, може спричинити дублювання тесту . Ти справді цього не хочеш, тому що нічого не шкодить більше, ніж тестовий набір, що сповільнює тебе. Це має скоротити час розробки та зменшити витрати на обслуговування! Якщо ви протестуєте приватні методи, які в іншому випадку перевіряються через загальнодоступний інтерфейс, тестовий набір може зробити протилежне і активно збільшити витрати на обслуговування та збільшити час розробки. Коли ви робите приватну функцію загальнодоступною або використовуєте щось на кшталт FRIEND_TEST
та / або відображення, зазвичай, в кінцевому рахунку, шкодуєте про це.
Розглянемо наступну можливу реалізацію Tokenizer
класу:
Скажімо, SplitUpByDelimiter()
відповідальність за повернення масиву таким чином, що кожен елемент масиву є маркером. Крім того, скажемо, що GetNextToken()
це просто ітератор над цим вектором. Отже, ваш громадський тест може виглядати так:
TEST_THAT(Tokenizer, canParseSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
ASSERT tokenizer.GetNextToken() IS "1";
ASSERT tokenizer.GetNextToken() IS "2";
ASSERT tokenizer.GetNextToken() IS "test";
ASSERT tokenizer.GetNextToken() IS "bar";
ASSERT tokenizer.HasMoreTokens() IS false;
}
Давайте зробимо вигляд, що у нас є те, що Майкл Перо називає інструментом затискання . Це інструмент, який дозволяє торкатися приватних частин інших людей. Приклад - FRIEND_TEST
з googletest або відображення, якщо мова підтримує його.
TEST_THAT(TokenizerTest, canGenerateSpaceDelimtedTokens)
{
input_string = "1 2 test bar"
tokenizer = Tokenizer(input_string);
result_array = tokenizer.SplitUpByDelimiter(" ");
ASSERT result.size() IS 4;
ASSERT result[0] IS "1";
ASSERT result[1] IS "2";
ASSERT result[2] IS "test";
ASSERT result[3] IS "bar";
}
Ну, скажімо, зараз вимоги змінюються, а токенізація стає набагато складнішою. Ви вирішили, що простого роздільника рядків не буде достатньо, і вам потрібен Delimiter
клас, щоб впоратися з роботою. Природно, ви очікуєте, що один тест зламається, але біль посилюється при тестуванні приватних функцій.
У програмному забезпеченні немає "одного розміру, який би відповідав усім". Іноді добре (і насправді ідеально) "порушувати правила". Я наполегливо рекомендую не перевіряти приватні функції, коли можете. Є дві основні ситуації, коли я думаю, що це нормально:
Я широко працював із застарілими системами (саме тому я такий великий фанат Майкла Пір'я), і я сміливо можу сказати, що іноді просто безпечно просто перевірити приватну функціональність. Це може бути особливо корисно для отримання "тестів на характеристику" в базовий рівень.
Ти поспішаєш і мусиш зробити найшвидше, що можливо, тут і зараз. Зрештою, ви не хочете перевіряти приватні методи. Але я скажу, що для вирішення проблем із дизайном зазвичай потрібен певний час на рефактор. І іноді доводиться відправляти через тиждень. Це нормально: робіть швидкі та брудні та випробовуйте приватні методи, використовуючи інструмент для обмацування, якщо це, на вашу думку, найшвидший і надійний спосіб виконати роботу. Але розумійте, що те, що ви зробили, було неоптимальним у довгостроковій перспективі, і, будь ласка, подумайте повернутися до нього (або, якщо про нього забули, але ви побачите це пізніше, виправте це).
Напевно, є й інші ситуації, коли це нормально. Якщо ви вважаєте, що це нормально, і у вас є хороше виправдання, тоді зробіть це. Ніхто вас не зупиняє. Просто будьте в курсі потенційних витрат.
Як осторонь, я дуже не люблю людей, які використовують TDD як привід для тестування приватних методів. Я практикую TDD, і не думаю, що TDD змушує вас це робити. Ви можете спочатку написати свій тест (для вашого загальнодоступного інтерфейсу), а потім написати код, щоб задовольнити цей інтерфейс. Іноді я пишу тест для публічного інтерфейсу, і це задовольняю, записуючи також один-два менших приватних методу (але приватні методи я не перевіряю безпосередньо, але я знаю, що вони працюють, або мій публічний тест був би невдалим ). Якщо мені потрібно перевірити кращі випадки цього приватного методу, я напишу цілу купу тестів, які потраплять на них через мій публічний інтерфейс.Якщо ви не можете зрозуміти, як вдарити крайові корпуси, це сильний знак, що потрібно переробляти на невеликі компоненти, кожен з яких має свої власні загальнодоступні методи. Це знак, що приватні функції ви робите занадто багато, і виходять за рамки класу .
Крім того, іноді я знаходжу, що я пишу тест, який на даний момент занадто великий, щоб пережовувати, і тому я думаю, "так, я повернусь до цього тесту пізніше, коли в мене буде більше API для роботи" (я Я прокоментую це і зроблю це в моїй думці). Ось тут багато спільнот, з якими я зустрічався, потім почнуть писати тести на їхню приватну функціональність, використовуючи TDD як козла відпущення. Вони кажуть "о, ну, мені потрібен інший тест, але для того, щоб написати цей тест, мені знадобляться ці приватні методи. Тому, оскільки я не можу написати жоден виробничий код без написання тесту, мені потрібно написати тест для приватного методу ". Але те, що їм насправді потрібно робити, - це рефакторинг на більш дрібні та багаторазові компоненти, а не додавання / тестування ряду приватних методів до їх поточного класу.
Примітка:
Я відповів на подібне запитання щодо тестування приватних методів за допомогою GoogleTest трохи раніше. Я в основному змінив цю відповідь, щоб бути тут більш агностичним мовою.
PS Ось відповідна лекція про уроки айсберга та інструменти для шпигування Майкла Пера: https://www.youtube.com/watch?v=4cVZvoFGJTU
_
, він сигналізує "ей, це" приватний ". Ви можете використовувати його, але повне розкриття, воно не було розроблене для повторного використання, і ви повинні використовувати його лише в тому випадку, якщо ви справді знайте, що ви робите ». Ви можете скористатися однаковим підходом на будь-якій мові: оприлюднити цих членів, але позначити їх провідними _
. Або, можливо, ці функції справді повинні бути приватними і просто перевірені через загальнодоступний інтерфейс (детальну інформацію див. У відповіді). Це від випадку до випадку, ніякого загального правила
Я думаю, що найкраще просто перевірити публічний інтерфейс об'єкта. З точки зору зовнішнього світу, має значення лише поведінка загальнодоступного інтерфейсу, і саме до цього слід спрямовувати ваші одиничні тести.
Після того, як для об’єкта написано кілька твердих тестових одиниць, ви не хочете повертатися назад і змінювати ці тести лише тому, що змінилася реалізація інтерфейсу. У цій ситуації ви зіпсували послідовність тестування свого пристрою.
Якщо ваш приватний метод не перевіряється за допомогою виклику ваших публічних методів, то що це робить? Я говорю про приватну, не захищену чи другу.
Якщо приватний метод є чітко визначеним (тобто він має функцію, яку можна перевірити і не має на меті змінюватись з часом), то так. Я перевіряю все тестувальне, де це має сенс.
Наприклад, бібліотека шифрування може приховати той факт, що вона виконує блокове шифрування за допомогою приватного методу, який шифрує одночасно лише 8 байт. Я б написав для цього тест на одиницю - це не призначено для зміни, навіть якщо він прихований, і якщо він справді зламається (наприклад, завдяки майбутнім підвищенням продуктивності), то я хочу знати, що ця приватна функція зламана, а не просто що одна з громадських функцій зламалася.
Це прискорює налагодження пізніше.
-Адам
Якщо ви розробляєте тестові методи (TDD), ви протестуєте свої приватні методи.
Я не є експертом у цій галузі, але одиничне тестування повинно перевірити поведінку, а не реалізацію. Приватні методи суворо є частиною впровадження, тому IMHO не слід перевіряти.
Ми перевіряємо приватні методи за допомогою висновку, під якими я маю на увазі, що ми шукаємо загальне покриття тестування класу щонайменше на 95%, але лише наші тести вимагають публічних чи внутрішніх методів. Щоб отримати покриття, нам потрібно робити декілька дзвінків до громадськості / внутрішніх служб, грунтуючись на різних сценаріях, які можуть виникати. Це робить наші тести більш уважними щодо мети коду, який вони тестують.
Відповідь Трампі на посаду, яку ви пов’язали, найкраща.
Я певний час готувався над цим питанням, особливо, намагаючись в TDD.
Я зіткнувся з двома повідомленнями, які, на мою думку, вирішують цю проблему досить ретельно у випадку TDD.
Підсумок:
При використанні тестових методів розробки (проектування) приватні методи повинні виникати лише під час процесу повторного факторингу вже працюючого та перевіреного коду.
За своєю сутністю процесу будь-який фрагмент простої функції реалізації, витягнутий із ретельно перевіреної функції, буде самоперевірений (тобто покриття непрямим тестуванням).
Мені здається досить зрозумілим, що на початку частини кодування більшість методів будуть функціями вищого рівня, оскільки вони інкапсулюють / описують дизайн.
Тому ці методи будуть загальнодоступними і перевірити їх буде досить просто.
Приватні методи з’являться пізніше, коли все буде добре, і ми будемо переосмислені заради читабельності та чистоти .
Як цитується вище, "Якщо ви не протестуєте свої приватні методи, то як ви знаєте, що вони не порушаться?"
Це головне питання. Один з важливих моментів одиничних тестів - це знати, де, коли і як щось прорвалося якнайшвидше. Таким чином, зменшується значна кількість зусиль з розвитку та забезпечення якості. Якщо все, що тестується, є загальнодоступним, то ви не маєте чесного висвітлення та розмежування внутрішніх справ класу.
Я знайшов один з найкращих способів зробити це - просто додати тестову посилання на проект і поставити тести в клас, паралельний приватним методам. Введіть відповідну логіку побудови, щоб тести не вбудовувалися в кінцевий проект.
Тоді ви маєте всі переваги тестування цих методів, і ви можете виявити проблеми за секунди проти хвилин або годин.
Отже, підсумовуючи, так, одиничне тестування ваших приватних методів.
Ви не повинні . Якщо ваші приватні методи мають достатню складність, яку потрібно перевірити, слід перенести їх до іншого класу. Зберігайте високу згуртованість , клас повинен мати лише одне призначення. Загальнодоступного інтерфейсу класу має бути достатньо.
Якщо ви не перевіряєте свої приватні методи, як ви знаєте, що вони не порушаться?
Це, очевидно, залежить від мови. Раніше, використовуючи c ++, я оголосив клас тестування як клас друзів. На жаль, це вимагає, щоб ваш виробничий код знав про клас тестування.
Я розумію точку зору, коли приватні методи розглядаються як деталі реалізації та їх не потрібно перевіряти. І я б дотримувався цього правила, якби нам довелося розвиватися лише поза об'єктом. Але ми, чи ми є якимись обмеженими розробниками, які розробляють лише поза об’єктами, називаючи лише їхні публічні методи? Або ми насправді також розробляємо цей об’єкт? Оскільки ми не зобов’язані програмувати зовнішні об'єкти, нам, ймовірно, доведеться залучати ці приватні методи до нових публічних, які ми розробляємо. Чи не було б чудово знати, що приватний метод протидіє всім шансам?
Я знаю, що деякі люди можуть відповісти, що якщо ми розробляємо інший публічний метод у цьому об'єкті, тоді цей метод повинен бути перевірений, і це все (приватний метод може продовжувати жити без тесту). Але це справедливо і для будь-яких публічних методів об’єкта: при розробці веб-програми всі публічні методи об’єкта викликаються з методів контролерів, і тому вони можуть розглядатися як деталі реалізації для контролерів.
То чому ми одиниці тестування об'єктів? Оскільки це справді важко, не можна сказати, неможливо бути впевненим, що ми тестуємо методи контролерів з відповідним входом, який запустить усі гілки базового коду. Іншими словами, чим вище ми в стеці, тим складніше перевірити всю поведінку. Так само і для приватних методів.
Для мене межа між приватним та публічним методами - це психологічний критерій, коли мова йде про тести. Критерії, які для мене важливіші:
Якщо я виявив, що приватний метод є величезним або складним або достатньо важливим, щоб вимагати власних тестів, я просто перекладаю його в інший клас і оприлюднюю його там (метод Object). Тоді я можу легко перевірити раніше приватний, але тепер публічний метод, який зараз живе на власному класі.
Я ніколи не розумію поняття Unit Test, але тепер я знаю, яка це мета.
Тест на одиницю не є повним тестом . Отже, це не заміна QA та ручного тестування. Концепція TDD в цьому аспекті помилкова, оскільки ви не можете протестувати все, включаючи приватні методи, а також методи, що використовують ресурси (особливо ресурси, які ми не маємо під контролем). TDD базує всю свою якість - те, чого не вдалося досягти.
Тест одиниці - це більше тест повороту. Ви відзначаєте деякий довільний шар, і результат зсуву повинен залишатися однаковим.
Загальнодоступні та приватні не є корисною відмінністю для того, який apis потрібно викликати з ваших тестів, а також метод проти класу. Більшість тестових одиниць видно в одному контексті, але приховано в інших.
Важливо - покриття та витрати. Вам потрібно мінімізувати витрати при досягненні цілей покриття вашого проекту (лінія, галузь, шлях, блок, метод, клас, клас еквівалентності, тематика використання ... все, що вирішить команда).
Тому використовуйте інструменти, щоб забезпечити охоплення, і спроектуйте свої тести, щоб викликати найменші витрати (коротко- та довгострокові ).
Не робіть тести дорожче, ніж потрібно. Якщо це найдешевше, лише тестувати громадські точки входу, зробіть це. Якщо тестувати приватні методи найдешевше, зробіть це.
Коли ви будете більш досвідчені, вам стане краще прогнозувати, коли варто рефакторинг, щоб уникнути довгострокових витрат на тест-обслуговування.
Якщо метод досить значний / досить складний, зазвичай я зроблю його «захищеним» і протестую. Деякі методи залишатимуться приватними та неявно перевірятимуться як частина одиниць тестів для публічно захищених методів.
Я бачу, що багато людей перебувають у тій же лінії мислення: тестування на громадському рівні. але хіба це не наша команда з QA? Вони перевіряють вхід і очікуваний вихід. Якщо ми, як розробники, перевіряємо лише публічні методи, то ми просто переробляємо завдання QA і не додаємо ніяких цінностей за допомогою «тестування одиниць».
Відповідь "Чи слід перевірити приватні методи?" є "....... іноді". Як правило, ви повинні тестувати інтерфейс своїх класів.
Ось приклад:
class Thing
def some_string
one + two
end
private
def one
'aaaa'
end
def two
'bbbb'
end
end
class RefactoredThing
def some_string
one + one_a + two + two_b
end
private
def one
'aa'
end
def one_a
'aa'
end
def two
'bb'
end
def two_b
'bb'
end
end
У RefactoredThing
вас тепер є 5 тестів, 2 з яких ви повинні були оновити рефакторінга, але його функціональність об'єкта дійсно не змінилося. Тож скажімо, що речі складніші за це, і ви маєте певний метод, який визначає порядок виводу, наприклад:
def some_string_positioner
if some case
elsif other case
elsif other case
elsif other case
else one more case
end
end
Це не повинно бути запущено стороннім користувачем, але ваш інкапсуляційний клас може бути важким, щоб запускати таку логіку через нього знову і знову. У цьому випадку, можливо, ви скоріше вилучите це в окремий клас, надайте цьому класу інтерфейс і протестуйте його.
І нарешті, скажімо, що ваш головний об’єкт надмірно важкий, а метод зовсім маленький, і вам справді потрібно переконатися, що результат правильний. Ви думаєте: «Я повинен перевірити цей приватний метод!». У вас що, можливо, ви можете зробити ваш об'єкт легшим, передавши частину важкої роботи як параметр ініціалізації? Тоді ви можете пройти щось легше і протестувати проти цього.
Один головний момент
Якщо ми перевіряємо правильність логіки, а приватний метод несе логіку, ми повинні перевірити її. Чи не так? То чому ми збираємось пропустити це?
Написання тестів, що базуються на наочності методів, абсолютно не має значення.
І навпаки
З іншого боку, головна проблема викликає приватний метод поза початковим класом. А також є обмеження щодо знущання над приватним методом у деяких знущальних інструментах. (Наприклад: Mockito )
Хоча є деякі засоби, такі як Power Mock, які це підтримують, це небезпечна операція. Причина полягає в тому, що для цього потрібно зламати СВМ.
Одна з проблем, яка може бути виконана (якщо ви хочете написати тестові випадки для приватних методів)
Оголосити ці приватні методи захищеними . Але це може бути не зручно для кількох ситуацій.
Йдеться не лише про державні чи приватні методи чи функції, це про деталі реалізації. Приватні функції - лише один аспект деталей реалізації.
Зрештою, тестування одиниць - це підхід до тестування білого поля. Наприклад, той, хто використовує аналіз покриття для виявлення частин коду, які були занедбані при тестуванні, переходить до деталей реалізації.
A) Так, вам слід протестувати деталі реалізації:
Подумайте про функцію сортування, яка з міркувань продуктивності використовує приватну реалізацію BubbleSort, якщо є до 10 елементів, і приватну реалізацію іншого підходу для сортування (скажімо, гипсорта), якщо є більше 10 елементів. Публічний API - це функція сортування. Однак ваш тестовий набір краще використовує знання про те, що насправді використовуються два алгоритми сортування.
У цьому прикладі, безумовно, ви могли б виконати тести на загальнодоступному API. Однак для цього потрібно мати ряд тестових випадків, які виконують функцію сортування з більш ніж 10 елементами, таким чином, щоб алгоритм згруповання був досить добре перевірений. Існування лише таких тестових випадків є свідченням того, що тестовий набір підключений до деталей реалізації функції.
Якщо відомості про реалізацію функції сортування змінюються, можливо, таким чином, що межа між двома алгоритмами сортування зміщується або ж замінюється сукупність на об'єднання або що завгодно: Існуючі тести продовжуватимуть працювати. Однак їх значення тоді сумнівне, і вони, ймовірно, повинні бути перероблені, щоб краще перевірити змінену функцію сортування. Іншими словами, будуть потрібні зусилля з обслуговування, незважаючи на те, що тести проходили на загальнодоступному API.
В) Як перевірити деталі реалізації
Однією з причин, чому багато людей стверджують, що не слід перевіряти приватні функції або деталі реалізації, є те, що більш детально змінити деталі реалізації. Ця більша ймовірність зміни хоча б є однією з причин приховування деталей реалізації за інтерфейсами.
Тепер припустимо, що реалізація за інтерфейсом містить більші приватні частини, для яких окремі тести на внутрішньому інтерфейсі можуть бути опцією. Деякі люди стверджують, що ці частини не слід перевіряти, коли вони приватні, їх слід перетворити на щось публічне. Після публічного, одиничного тестування цього коду було б добре.
Це цікаво: Хоча інтерфейс був внутрішнім, він, ймовірно, змінився, будучи деталлю реалізації. Беручи той самий інтерфейс, оприлюднюючи його, відбувається деяка магічна трансформація, а саме перетворення його в інтерфейс, який менш імовірно зміниться. Очевидно, є певна вада в цій аргументації.
Але за цим є деяка правда: під час тестування деталей реалізації, зокрема, з використанням внутрішніх інтерфейсів, слід прагнути використовувати інтерфейси, які, ймовірно, залишаються стабільними. Чи може бути якийсь інтерфейс стабільним, однак, не просто вирішується, виходячи з того, є державним чи приватним. У проектах зі світу, над якими я працюю певний час, публічні інтерфейси також досить часто змінюються, і багато приватних інтерфейсів залишаються недоторканими століттями.
І все-таки є правильним принципом використання "вхідних дверей спочатку" (див. Http://xunitpatterns.com/Principles%20of%20Test%20Automation.html ). Але майте на увазі, що це називається «вхідні двері спочатку», а не «лише вхідні двері».
В) Підсумок
Перевірте також деталі реалізації. Віддайте перевагу тестуванню на стабільних інтерфейсах (державних або приватних). Якщо відомості про впровадження змінюються, тести на загальнодоступному API повинні бути переглянуті. Перетворення чогось приватного в публічне не змінює магічної стабільності.
Так, ви повинні перевірити приватні методи, де це можливо. Чому? Щоб уникнути непотрібного вибуху в просторі стану тестових випадків, який у кінцевому підсумку просто неявно тестує ті ж приватні функції повторно на одних і тих же входах. Пояснимо, чому на прикладі.
Розглянемо наступний трохи надуманий приклад. Припустимо, ми хочемо відкрити публічно функцію, яка займає 3 цілих числа і повертає істину, якщо і лише тоді, коли ці 3 цілі числа є простими. Ми можемо реалізувати це так:
public bool allPrime(int a, int b, int c)
{
return andAll(isPrime(a), isPrime(b), isPrime(c))
}
private bool andAll(bool... boolArray)
{
foreach (bool b in boolArray)
{
if(b == false) return false;
}
return true;
}
private bool isPrime(int x){
//Implementation to go here. Sorry if you were expecting a prime sieve.
}
Тепер, якби ми дотримувались суворого підходу, що слід перевіряти лише загальнодоступні функції, нам було б дозволено протестувати, allPrime
а не isPrime
або andAll
.
В якості тестера, ми могли б бути зацікавлені в п'ять можливостей для кожного аргументу: < 0
, = 0
, = 1
, prime > 1
, not prime > 1
. Але якщо бути ретельним, ми також повинні побачити, як поєднується кожна комбінація аргументів. Тож це 5*5*5
= 125 тестових випадків, нам потрібно було б ретельно перевірити цю функцію відповідно до наших інтуїцій.
З іншого боку, якби нам дозволили протестувати приватні функції, ми могли б покрити стільки ж ґрунту за допомогою меншої кількості тестових випадків. Нам знадобиться лише 5 тестових випадків, щоб перевірити isPrime
той же рівень, що і попередня інтуїція. І за гіпотезою про малий обсяг, запропонованою Деніелом Джексоном, нам потрібно було б лише протестувати andAll
функцію до невеликої довжини, наприклад, 3 або 4. Що було б не більше 16 тестів. Так 21 тест загалом. Замість 125. Звичайно, ми, мабуть, хотіли б виконати кілька тестів allPrime
, але ми не будемо так зобов’язані вичерпно висвітлити всі 125 комбінацій вхідних сценаріїв, про які ми говорили, що нас хвилювали. Всього кілька щасливих стежок.
Надуманий приклад, напевно, але це було необхідно для чіткої демонстрації. І модель поширюється на реальне програмне забезпечення. Приватні функції, як правило, будівельні блоки найнижчого рівня, і тому часто поєднуються разом, щоб отримати логіку вищого рівня. Значення на більш високих рівнях, у нас більше повторень матеріалів нижчого рівня завдяки різним комбінаціям.
isPrime
справді незалежні, тому тестувати кожну комбінацію наосліп є досить безцільним. По-друге, маркування чистої функції під назвою isPrime
приватне порушує стільки правил дизайну, що я навіть не знаю, з чого почати. isPrime
дуже чітко має бути публічною функцією. Попри це, я розумію, що ви говорите, незалежно від цього надзвичайно бідного прикладу. Тим НЕ менше він побудований від передумову ви хочете зробити комбіноване тестування, коли в реальних системах програмного забезпечення це рідко хороша ідея.