Спротив автоматизації тестування

Попри те, що технології модульного тестування у програмному забезпеченні розвиваються вже понад тридцять років (зокрема, у 1989 році Кент Бек опублікував статтю “Simple Smalltalk Testing: With Patterns”), досі не всі програмісти опанували ці підходи, а чимало компаній так і не інтегрували автоматичне тестування у власну корпоративну культуру. Незважаючи на очевидні переваги автоматизованого тестування, зберігається суттєвий поведінковий опір. Досвід практичного впровадження свідчить: майже завжди знаходяться причини, які унеможливлюють або суттєво ускладнюють його застосування.

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

Усі ці заперечення я систематизував у вигляді піраміди надійного програмування, яка охоплює чотири рівні:

  1. Професійна культура (найвищий рівень, фундамент надійного програмування) — сукупність норм, неписаних правил та переконань, якими керується фахівець у своїй роботі. Приклади: «Надсилати у репозиторій код, не покритий тестами, — неприпустимо»; «Замовчувати виявлені помилки у коді — соромно».
  2. Менеджмент — формалізовані процедури, політики та правила, ухвалені в організації, а також воля та рішення керівництва. Наприклад: «Кожна розроблена функція додатка має пройти код-ревʼю. Без винятків!»
  3. Методи — наукові підходи й способи розвʼязання конкретних завдань. Наприклад: «Якщо функцію складно протестувати, слід підвищити тестопридатність застосунку, застосувавши шаблон Dependency Injection».
  4. Технології (найнижчий рівень) — мови програмування, бібліотеки, фреймворки та інструменти. Приклади: JUnit, Selenium, XCTest тощо.

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

Заперечення культурного рівня

«Мої програми не ламаються. Я не бачу потреби у тестуванні»

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

Крім того, подібні заперечення часто походять від новачків, які взагалі не мають уявлення про тестування. Для них «помилка» — це виключно аварійне завершення програми (креш), але не функціональні збої чи некоректна логіка.

Показовим є діалог на одному зі співбесід, які я проводив:

— Ви володієте навичками автоматизованого тестування?

— Ні, я писав прості програми, там не було чому ламатися.

— У чому ваша мотивація змінити місце роботи?

— Я хочу створювати складні застосунки.

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

Небажання брати відповідальність за якість коду та тестування

Автоматизовані тести є доступним джерелом оперативної та обʼєктивної інформації щодо реальної якості програмного продукту. Інакше кажучи, «за спиною» програміста завжди присутній своєрідний наглядач, який у будь-який момент може надати керівництву звіт про те, наскільки якісно виконується робота. Автоматизоване тестування дозволяє співвіднести результативність праці не лише із закритими завданнями в Jira, а насамперед із фактичним рівнем якості продукту. Це вимагає іншого підходу: програміст має писати код надійно, так щоб кожна зміна не руйнувала вже реалізовані функції, а нова функціональність коректно працювала як у штатних сценаріях, так і при обробці помилок.

Відповідальність у цьому контексті — це добровільне прийняття зобовʼязань забезпечити позитивний результат праці. Співробітник бере на себе таке зобовʼязання залежно від власних особистісних якостей і професійного виховання. На жаль, унаслідок культурної та професійної кризи не кожен програміст готовий взяти на себе таку відповідальність.

«Пишіть одразу правильно, без помилок»

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

— Давайте покриємо застосунок автоматизованими тестами.

— Навіщо?

— Щоб упевнитися, що все працює коректно й немає помилок.

— Ви пишете з помилками? У вас низька кваліфікація? Пишіть одразу правильно, без помилок.

— Так, але помилки допускають усі…

— А ось інша компанія XYZ запевнила, що в них працюють топпрограмісти, які пишуть код без помилок!

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

Заперечення на рівні менеджменту

«З тестами програму писати вдвічі довше. Ми не вкладемося в терміни»

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

Автоматизовані тести виконують кілька ключових функцій:

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

У практиці моїх консультацій траплялися показові діалоги. В одній організації менеджер відмовлявся впроваджувати автоматизоване тестування:

— Але ж писати тести довго! Ми не вкладемося в терміни!

— Чи були у вас помилки, на пошук і виправлення яких пішло дуже багато часу?

— Так, були.

— Який найскладніший випадок?

— Одну помилку ми шукали 80 годин.

— Вісімдесят годин — це два тижні роботи програміста. Якби ви витратили навіть тиждень на автоматизацію тестування, це зекономило б вам місяці на діагностику та налагодження вашої системи!

У нашій організації Design and Test Lab діє постулат: «З тестами програму писати вдвічі швидше!» — і це твердження не підлягає сумніву. Дискусійним є лише коефіцієнт «2»: іноді він становить і «3», і «4». А деякі проєкти без грамотного автоматизованого тестування просто неможливо завершити (повірте мені на слово або згадайте приклади провалених проєктів з вашого досвіду).

«У нас уже є відділ ручного тестування — нехай вони й тестують»

На перший погляд, поділ спеціалізацій на тестування та програмування видається логічним.

Проте розгляньмо основні недоліки ручного тестування:

  • Висока вартість. Виконання великої кількості тестів вручну потребує значних людських ресурсів.
  • Тривалість виконання. Наприклад, на створення та виконання тестових сценаріїв для мобільного застосунку «Онлайн-кінотеатр» тестувальник витрачає 40 годин. І це лише для однієї платформи! Якщо ж необхідно протестувати застосунок на iPhone, iPad, Apple TV, Android, Fire TV, то сумарні витрати становлять 40 × 6 = 240 годин, тобто півтора місяця робочого часу. Для коротких циклів розробки це неприйнятно.
  • Людський фактор. Ручне тестування схильне до субʼєктивності та звичайних помилок, отже, воно не забезпечує повністю обʼєктивних результатів.

Крім того, певні види тестів практично неможливо виконати у прийнятні строки через величезну кількість комбінацій форматів та сценаріїв. Серед характерних прикладів:

  • Функціонал імпорту CSV-файлів або інших файлів — кількість можливих форматів, кодувань і структур даних практично безмежна.
  • Парсери текстових документів — величезна варіативність синтаксису, розмітки та виняткових випадків у вхідних файлах.
  • Фінансові інструменти — потреба в абсолютній точності обчислень і великій кількості сценаріїв перевірки.

Заперечення на рівні методів

Невідомість методів автоматизованого тестування

Унаслідок кризи у сфері освіти у вищих навчальних закладах практично відсутні дисципліни, присвячені автоматизованому тестуванню. Аналогічна ситуація і в комерційних ІТ-школах: відповідних курсів украй мало, а їхній рівень зазвичай поверховий і низької якості. Тому доволі часто програмісти демонструють «ступор», адже не знають, як протестувати нетривіальні застосунки (складніші за рівняння «2 + 2 = 4»).

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

а) що таке тестопридатність?

б) що таке керованість і спостережуваність?

в) які шаблони проєктування підвищують тестопридатність застосунку?

і багато інших.

Програмісти не знають, що саме вони створюють, як це виглядатиме та які функції й інтерфейси матиме

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

Особливістю деяких проєктів є розробка за принципом Minimum Viable Product (MVP), який можна коротко охарактеризувати так: «Зробімо бодай щось у мінімальні терміни та за мінімальний бюджет». При цьому замовник або менеджмент часто розглядають розробника як аналітика, дизайнера, архітектора, програміста й тестувальника «в одній особі». Такий підхід виключає формальний етап проєктування програмної системи: визначення бізнес-логіки, предметної області, інтерфейсів компонентів і внутрішньої організації їхніх взаємозвʼязків. У результаті відсутні формалізована архітектура, чіткі інтерфейси та прописані бізнес-процеси — тож незрозуміло, що саме тестувати, через які інтерфейси та який очікувати результат.

Нетестопридатний код

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

  • Керованість (controllability) — наскільки легко ввести застосунок у необхідний стан для проведення тестування (задати передумови).
  • Спостережуваність (observability) — наскільки легко зчитати стан після виконання тесту й порівняти його з очікуваним.

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

Стикаючись із нетестопридатним кодом, розробник часто опускає руки й уникає тестування такої системи.

Підготовка тестових даних

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

Рішення:

  • розробка еталонних значень і прикладів на етапі створення приймальних тестів — вони також допомагають уникнути конфліктів із замовником під час приймання робіт;
  • створення еталонних значень на етапі проєктування системи (наприклад, еталонні HTTP-запити та відповіді), що спрощує інтеграцію клієнта й сервера;
  • розробка спеціальних процедур збирання баз даних, за яких потрібний стан бази створюється автоматично, а не вручну;
  • використання шаблону Object Mother (Fowler, Schuh, Peter, and Stephanie Punke. Easing Test Object Creation in XP. XP Universe, 2003), що дозволяє легко створювати та ініціалізувати обʼєкти у потрібному стані.

Супровід тестів

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

Рішення:

  • застосування шаблону «адаптер», щоб відвʼязати логіку тесту від конкретного інтерфейсу, який він перевіряє;
  • використання високорівневих тестів (Gherkin, Cucumber, Given-When-Then); див. також рішення до розділу «Підготовка тестових даних».

Висновок

Без сумніву, програмне забезпечення має бути надійним і перевищувати очікування користувачів. Автоматизовані тести — хоча й не єдиний, проте надзвичайно важливий компонент у процесі створення надійних програмних систем.

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

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

  • формування та популяризацію нової культури ІТ-проєктування, що ґрунтується на надійності, професійній гордості та персональній відповідальності за результат;
  • розробку нових високих стандартів тестування коду;
  • створення й впровадження навчальних курсів;
  • запровадження мотиваційних механізмів у карʼєрному зростанні програмістів і менеджерів, повʼязаних із якістю створюваних програмних продуктів та рівнем володіння методами автоматизованого тестування.

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

Ця лекція є частиною онлайн-курсу Test Driven Development з Python, на який ще не відкрито запис. Долучайтеся до списку очікування, щоб дізнатися, коли він розпочнеться! 🚀