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

Яку користь дають unit тести і як впевнитись що ця користь взагалі є?

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

Іншою метрикою можна назвати можливість легко змінювати систему. Якщо у вас є код повністю непокритий unit тестами – то вам страшно вносити будь-які зміни в систему, і тому набагато простіше скопіювати кусок код і помістити поряд таку ж реалізацію. Якщо ж у вас є тести, то ви можете більш-менш безболісно змінити код і перевірити чи система продовжує працювати коректно. Представте що у вас є велика система, і як перевірити що вона працює правильно і так як задумано? Для цього її потрібно протестувати. Ви запускаєте весь цикл регресивного тестування залучивши до цього QA. Після внесення змін не можна бути впевненим, що система все так же якісно працює, тому треба знову все тестувати. Чим більше регресивного і ручного тестування – тим дорожчою являється розробка, а програміст пізніше отримує інформацію про зміни в системі. Unit тести – це самий простий і близький до інженера спосіб сказати що система все ще якісна після того, як були внесені зміни.

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

В спільноті програмістів немає метрик, які б гарантували що використання unit тестів збільшить якість коди на 30%, наприклад, але емпірично було вирахувано, що тести збільшують якість коду. Побачити важливість тестів можна на тенденції створення нових мов і фреймворків, які зразу розробляються з вбудованими інструментами для unit тестів, наприклад Go мова.

Чи збільшує час розробки написання тестів?

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

  • фінальна реалізація буде набагато менша. У вас не включиться “режим архітектора” і не будете робити такі речі як реалізація на всі випадки життя. TDD каже, щоб ви робили тільки те що потрібно в цій задачі і нічого більше.
  • економиться час видумуванням способів як провалідувати код, не треба запускати всю системи доки не буде повністю завершена задача.
  • велику частину коду можна згенерувати з тесту. Сучасні IDE дуже розвинені. Не потрібно про це забувати. Наприклад в Java або .NET можна згенерувати частину коду по певному сценарію. Це економить багато часу.

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

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

Хороші unit тести повинні відповідати наступним критеріям:

  1. Вони прості й зрозумілі. Якщо ви через місяць дивитесь на свій тест або тест свого колеги і думаєте що краще я його видалю і напишу новий – це уже неправильно. Якщо дивитесь на код і розумієте що тест ще складніший – це теж не те що треба.
  2. Характеристика Unit тестів. Вони повинні забезпечувати Quality gate – захищати вас від допущених помилок, якщо ви пішли й змінили щось в чому не дуже розібралися. Для цього вони повинні бути ізольованими.
  3. Найменування тестів. Вони повинні казати що впали та чому. Тести потрібно ділити на маленькі ізольовані частини.
  4. Unit тест який не можна відрізнити хто з команди написав. Повинна бути консистентність. Уникати ситуацій коли дивишся на тест test1, test2, test3 і розумієш що це написав Паша, тут все навалено купою і нічого незрозуміло, а цей тест написаний Сашею – тут все харашо. Для цього потрібно залучити Code review і статичний аналіз коду. Тести потрібно перевіряти так само як і основний код.
  5. Unit тести повинні бути без side ефектів. Вони повинні бути незалежні і запускатися окремо.
  6. Читабельність. Коли є вхідні параметри, виконусь ось це і результат такий. Це створює автодокументацію коду і допомагає робити code reveiw більше ефективним. Review юніт тестів разом з кодом допомагає зрозуміти які use кейси були покриті, чи все реалізовано.
  7. Чисті тести. Все з основ чистого коду. Повинен читатися легко, навіть якщо тестує складний код.
  8. Тести повинні бути швидкими. Вони не повинні конектитись до бази даних, щось отримувати з мережі чи читати з файлової системи. Навіть на дуже великому проекті всі тести повинні проходити не довше декількох хвилин.
  9. Тести повинні перевіряти ЩО робить код а не ЯК він це робить. Щоб була можливість код змінити або порефакторити.
  10. Unit тести повинні бути іменно Unit тестами а не інтеграційними, наприклад. Тулзи дозволять писати інтеграційні тести, які ми називаємо Unit тестами, а потім жаліємось чому вони такі нестабільні.
  11. Намагатися зберігати пірамуду тестування. Unit тестів повинно бути багато, а тестів вищого рівня менше. Часто на проектах пишуть багато інтеграційних тестів і мало unit – це антипатерн, перевернута піраміда.

Які підходи до найменування тестів?

Колись давно, коли ще були обмеження в unit testing frameworks то рекомендації були такими що кожен тест повинен починатися зі слова test (зараз це абсолютний нонсенс), далі щоб розуміти який метод тустується рекомендувалось включати ім’я метода і дальше вказувалось який аспект ми тестуємо. Коли ці необхідності відпали, то правильним способом стало іменувати сценарій, наприклад account value should remain if payment is unavailable.

Яким повинен бути код щоб його було легко тестувати?

Коли ми думаємо що код має бути протестованим, ми автоматично його пишемо краще. Притримуємось SOLID принципів, відділяємо UI від логіки… Щоб написати unit тест харашо коли компонент який тестується – ізольований. Тут добре працює inversion of control, dependecy injection, коли клас не відповідає за те щоб отримати залежності, а вони йому надаються контейнером в якому він працює.

Корисні матеріали:

Mocks Aren’t Stubs

Частина 2 – Unit tests. Частина 2.

Схожі статті