Чи краще код для тестування?


103

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

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

Чи написання тестового коду все ще є хорошою практикою навіть за відсутності тестів?


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


24
Існує особлива властивість функції, яка вимагає передати час, який називається idempotency. Така функція буде отримувати один і той же результат кожного разу, коли вона викликається із заданим значенням аргументу, що не лише робить її більш перевіряемою, але й більш складною та легшою для міркування.
Роберт Харві

4
Чи можете ви визначити "кращий код"? ти маєш на увазі "ремонтопридатний" ?, "простіший у використанні-без-МОК-контейнер-магія"?
k3b

7
Я думаю, у вас ніколи не було тесту, оскільки він використовував фактичний системний час, а потім змінено часовий пояс.
Енді

5
Це краще, ніж нестабільний код.
Tulains Córdova

14
@RobertHarvey Я б не називав цю idempotency, я б сказав, що це референтна прозорість : якщо func(X)повертається "Morning", то заміна всіх випадків func(X)на "Morning"не змінить програму (тобто виклик funcне робить нічого іншого, крім повернення значення). Idempotency має на увазі або те func(func(X)) == X(що не відповідає правильності типу), або те, що func(X); func(X);виконує ті ж побічні ефекти, що і func(X)(але побічних ефектів тут немає)
Warbo

Відповіді:


116

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

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

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


3
Коментарі не для розширеного обговорення; ця розмова переміщена до чату .
Світовий інженер

2
Я думаю, що варто відзначити, що я знаю принаймні одну мову, яка дозволяє робити те, що описує ваш останній абзац: Python with Mock. Через те, як працює імпорт модулів, майже все, крім ключових слів, можна замінити макетом, навіть стандартні методи API / класи / тощо. Це можливо, але це може зажадати, щоб мова була побудована таким чином, щоб підтримувати таку гнучкість.
jpmc26

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

2
Деякі свої думки я висловив у коментарях статті (оскільки розширені коментарі тут не дозволені), перевірте це! Щоб було зрозуміло: я є автором згаданої статті :)
Сергій Колодій

Я повинен погодитися з @BryanOakley. "Тестовий код" припускає, що ваші занепокоєння розділені: можна протестувати аспект (модуль) без втручання з інших аспектів. Я б сказав, що це відрізняється від "коригування ваших проектів підтримки конкретних умов тестування". Це схоже з моделями дизайну: їх не слід змушувати. Код, який правильно використовує шаблони дизайну, вважатиметься сильним кодом. Те саме стосується принципів тестування. Якщо ваш код "перевіряється" призводить до надмірного скручування коду проекту, ви робите щось не так.
Вінс Емі

68

Чи написання тестового коду все ще є хорошою практикою навіть за відсутності тестів?

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

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

Тому для мене завжди є набір пріоритетів у написанні коду:

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

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


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

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

4
@WannabeCoder Звичайно, варто додати гнучкість лише тоді, коли це заощадить ваш час. Ось чому ми не пишемо кожен метод проти інтерфейсу - більшість часу просто простіше переписати код, а не включити занадто велику гнучкість від початку. YAGNI все ще є надзвичайно потужним принципом - просто переконайтесь, що все, що ви "вам не знадобиться", додавання цього заднім числом не дасть вам більше роботи в середньому, ніж впровадження його достроково. Код, який не відповідає YAGNI, має найбільшу гнучкість у моєму досвіді.
Луань

3
"Якщо ви не маєте одиничних тестів, це означає, що ви не закінчили з кодом / функцією" - Неправда. «Визначення зробленого» - це те, що вирішує команда. Він може або не може включати певну ступінь покриття тесту. Але ніде не існує суворої вимоги, яка говорить про те, що функцію неможливо виконати, якщо для неї немає тестів. Команда може вирішити вимагати тестів, а вони не можуть.
aroth

3
@Telastyn За більш ніж 10 років розвитку я ніколи не мав команди, яка б мандатувала одиничне тестування рамки, і лише дві, які навіть мали одну (обидва мали слабке покриття). Одне місце вимагало текстового документа про тестування функції, яку ви писали. Це воно. Можливо, мені не пощастило? Я не анти-підроздільний тест (серйозно, я мода на сайті SQA.SE, я дуже про тест!), Але я не знайшов їх настільки поширеними, як стверджує ваше твердження.
corsiKa

50

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

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

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

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


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

10
Nobody cares about the tests themselves-- Я згоден. Я вважаю тести кращою документацією того, що робить код, ніж будь-які файли коментарів чи readme.
jcollum

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

12

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

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

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

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

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

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


10

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

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

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

Окрім того, що цей маленький метод не є гнучким, має два обов'язки: (1) отримання системного часу, а потім (2) повернення деякої цінності на його основі.

public static string GetTimeOfDay()
{
    DateTime time = DateTime.Now;
    if (time.Hour >= 0 && time.Hour < 6)
    {
        return "Night";
    }
    if (time.Hour >= 6 && time.Hour < 12)
    {
        return "Morning";
    }
    if (time.Hour >= 12 && time.Hour < 18)
    {
        return "Afternoon";
    }
    return "Evening";
}

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


1
Тож вам доведеться пройти тестування рано вранці, щоб перевірити, чи отримаєте ви результат "Ніч", коли цього захочете. Це важко. Тепер припустимо, що ви хочете перевірити правильність обробки дат 29 лютого 2016 року ... І деякі програмісти iOS (і, мабуть, інші) зазнають помилки початківця, яка псує річ незадовго до початку року чи після нього, як ви тест на те. І з досвіду я перевіряю обробку дат 2 лютого 2020 року.
gnasher729

1
@ gnasher729 Точно моя думка. "Зробити цей код перевіряючим" - це проста зміна, яка може вирішити безліч проблем (тестування). Якщо ви не хочете автоматизувати тестування, я думаю, що код проходить як є. Але було б краще, як тільки це "перевіряється".
Ерік Кінг

9

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

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

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

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


+1 - вам потрібно вдосконалити дизайн та архітектуру, щоб полегшити тести написання одиниць.
BЈовић

3
+ - це важлива архітектура вашого коду. Простіше тестування - лише щасливий побічний ефект.
gbjbaanb

8

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

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

Так. Добре практика писати тестовий код, навіть незалежно від тестування.


не погоджуються з тим, що це DRY - обгортання GetCurrentTime у методі MyGetCurrentTime дуже багато разів повторює виклик ОС без жодної користі, окрім як допомогти інструменту тестування. Ось лише найпростіші приклади, вони набагато гірші в реальності.
gbjbaanb

1
"повторна виклик ОС без поваги" - доки ви не закінчите працювати на сервері одним годинником, розмовляючи з aws-сервером в іншому часовому поясі, і це порушує ваш код, і тоді вам доведеться пройти весь код і оновіть його, щоб використовувати MyGetCurrentTime, який замість цього повертає UTC. ; перекос годинника, перехід на літнє світло та інші причини, через які, можливо, не буде гарною ідеєю сліпо довіряти виклику ОС або, принаймні, мати єдину точку, куди можна відмовитися від іншої заміни.
Ендрю Хілл

8

Написання тестового коду важливо, якщо ви хочете довести, що ваш код насправді працює.

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

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

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

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

Тож наближається до мого резюме, чи тестовий код кращий код?

Я не знаю, може, й ні. У людей тут є деякі дійсні моменти.

Але я вважаю, що кращий код також має бути перевіреним кодом.

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

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


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

1
Саме так. Розглянемо контрастність. Якщо це незаперечний код, він не перевіряється. Якщо його не перевіряють, то як ви знаєте, працює він чи ні, окрім як у живій ситуації?
pjc50

1
Все тестування доводить, що код проходить тести. В іншому випадку перевірений модуль код не буде помилок, і ми знаємо, що це не так.
wobbily_col

@anaximander Рівно. Принаймні є можливість, що сама наявність тестів є протипоказанням, що призводить до погіршення коду якості, якщо вся увага зосереджена саме на встановленні прапорців. "Принаймні сім одиничних тестів для кожної функції?" "Перевірити". Але я дійсно вірю, що якщо код є якісним кодом, це буде простіше перевірити.
Крейг

1
... але зробити бюрократію з тестування може бути загальним відходом і не дати корисної інформації або достовірних результатів. Незалежно; Я впевнений, бажаю, щоб хтось випробував помилку на SSL Heartbleed , так? чи помилка Apple goto fail ?
Крейг

5

Мені, однак, здається, що надмір передати час як аргумент.

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

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

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

def time_of_day(now=None):
    now = now if now is not None else datetime.datetime.utcnow()
    return now.strftime('%H:%M:%S')

Якщо Python підтримує стрибкові секунди, тестовий код виглядатиме так:

def test_handle_leap_second(self):
    actual = time_of_day(
        now=datetime.datetime(year=2015, month=6, day=30, hour=23, minute=59, second=60)
    expected = '23:59:60'
    self.assertEquals(actual, expected)

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

def time_of_day():
    return datetime.datetime.utcnow().strftime('%H:%M:%S')

Код тесту:

@unittest.patch('datetime.datetime.utcnow')
def test_handle_leap_second(self, utcnow_mock):
    utcnow_mock.return_value = datetime.datetime(
        year=2015, month=6, day=30, hour=23, minute=59, second=60)
    actual = time_of_day()
    expected = '23:59:60'
    self.assertEquals(actual, expected)

Це дає кілька переваг:

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

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


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

4

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

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

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


4

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

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

Чому це так? Я не думаю, що я можу зрозуміти і пояснити всі причини. Але одна з причин полягає в тому, що хороша розбивка незмінно означає вибір помірного "розміру зерна" для одиниць реалізації. Ви не хочете втиснути занадто багато функціональності та занадто багато логіки в один клас / метод / функцію / модуль / тощо. Це робить ваш код легшим для читання та легшим записом, але також полегшує тестування.

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

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

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

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


1

Якщо ви збираєтеся з ТВЕРДИМИ принципами ви будете на хорошій стороні, особливо якщо продовжити це з поцілунками , СУХИЙ і YAGNI .

Одним з недоліків для мене є питання складності методу. Це простий метод геттера / сетера? Тоді просто написання тестів для задоволення ваших тестових рамок буде марною тратою часу.

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

Ще один аспект написання тестів - показати іншим розробникам, як використовувати ваш метод. Багато разів розробник шукає приклад того, як використовувати метод і яким буде повернене значення.

Всього два мої копійки .

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