Регресија — је баг који се појављује након уношења промена у код, иако је раније иста функционалност радила исправно. Регресија значи да је нова промена „покварила“ оно што је већ било написано и тестирано раније. Ово је један од најчешћих и најопаснијих проблема у развоју: поправљајући један баг, програмер може неприметно покварити три друге функције. Према Capers Jones Software Engineering 2023, просечна густина регресионих багова је 1–3 на сваких 100 измењених линија кода. Разматрамо узроке регресија, методе њиховог откривања и стратегије превенције.
Главно
Регресија — је ситуација када функционалност која је радила у претходној верзији престаје да ради након уношења промена. Промена може бити било која: исправка бага, додавање нове функције, рефакторисање, ажурирање библиотеке или чак промена конфигурације. Регресија је главни непријатељ стабилности: свака промена ризикује да поквари нешто што је већ проверено и објављено.
Термин потиче из тестирања: регресионо тестирање — је поновно покретање постојећих тестова након сваке промене. Ако тест који је раније пролазио падне — значи да је дошло до регресије. У ширем смислу, регресија није само пад теста, већ и било какво погоршање понашања које примети корисник или QA. Према Tricentis State of Testing 2023, регресије чине 35–45% свих багова пронађених у продукцији.
Регресија се од обичног бага разликује по временском контексту: баг је могао постојати одувек, а регресија је увек резултат промене. Ово је важна разлика, јер потрага за узроком регресије почиње анализом промена: шта је промењено између „радило је“ и „престало је да ради“. Git bisect — стандардни алат за проналажење комита који је изазвао регресију.
Локална регресија — промена у модулу А квари функционалност у истом модулу А. Пример: програмер преписује функцију сортирања, и она престаје да исправно обрађује празан низ. Локална регресија је најлакша за откривање и исправљање, јер су узрок и последица близу.
Удаљена регресија — промена у модулу А квари функционалност у модулу Б, који није директно повезан кодом, али је повезан подацима или временом. Пример: промена шеме базе података у модулу „Корисници“ квари извештај у модулу „Аналитика“ који користи исту табелу. Удаљене регресије су најподмуклије: програмер не сумња да ће његова промена утицати на други модул.
Регресија нуспојава — промена нуспојаве (логовање, кеширање, слање обавештења) квари очекивано понашање. Пример: програмер је додао кеширање ради убрзања, али због застарелог кеша корисници виде застареле податке. Регресије нуспојава је тешко хватати аутоматским тестовима, јер нуспојаве често нису покривене тестовима.
Перформанс регресија — код наставља да ради исправно функционално, али спорије него раније. Пример: нови алгоритам за шифровање даје исте резултате, али време извршења је порасло са 2 ms на 200 ms. Перформанс регресије се не хватају обичним unit тестовима — потребни су бенчмаркови и профилисање.
| Тип регресије | Пример | Начин откривања |
|---|---|---|
| Локална | Покварено сортирање | Unit тестови |
| Удаљена | Промена шеме БП | Интеграциони тестови |
| Нуспојава | Застарео кеш | E2E тестови |
| Перформанс | Успоравање одговора | Бенчмаркови |
Први узрок — повезаност кода (coupling). Што су модули више зависни једни од других, већа је вероватноћа да ће промена у једном изазвати регресију у другом. Класични антиобрасци: God Object (објекат који ради све), Shotgun Surgery (промена једног захтева исправке на десетинама места), Circular Dependency. Смањење coupling-а — задатак архитектуре: SOLID принципи, Dependency Injection, хексагонална архитектура.
Други узрок — недостатак тестова за измењену функционалност. Ако код није покривен тестовима, програмер сазнаје за регресију тек од QA или корисника. Према Google Testing Blog, пројекти са покривеношћу тестова >75% имају 5 пута мање регресија од пројеката са покривеношћу <25%. TDD (Test-Driven Development) гарантује да су тестови написани пре кода, а не „кад буде времена“.
Трећи узрок — људски фактор. Програмер не зна за постојање суседне функционалности, не разуме све зависности или једноставно жури. Узрок — недовољна размена знања о бази кода. Решења: code review са учешћем програмера из других модула, pair programming, документација архитектуре. Bus factor пројекта је обрнуто пропорционалан броју документованих архитектонских одлука.
Регресионо тестирање — је процес поновног покретања постојећих тестова након сваке промене ради провере да стара функционалност није покварена. Ово је једини начин да се гарантује да нова промена није нарушила рад постојећег кода. Без регресионог тестирања, свако издање је лутрија: програмер се нада да ништа није покварио, али не може то да потврди.
Ручно регресионо тестирање — најскупљи и неефикасан приступ. Како пројекат расте, број регресионих тест сценарија расте линеарно, а време за њихово ручно покретање — експоненцијално. Након 2–3 године развоја, ручно покретање регресије може трајати 2–3 недеље, што чини честа издања немогућим. Једини излаз је аутоматизација.
Аутоматизовано регресионо тестирање дели се на нивое према пирамиди тестирања:
Према Google Testing Blog, оптимални однос: 70% unit тестова, 20% интеграционих, 10% E2E. Одступање од ове пропорције смањује ефикасност регресионог тестирања: вишак E2E тестова успорава цевовод, недостатак unit тестова оставља микробагове непримећеним.
Прва стратегија — Full Regression. Покрећу се сви тестови пројекта. Најпоузданији, али и најспорији приступ. Примењив за мале пројекте (до 10 000 тестова, време покретања <30 минута). За велике пројекте, пуна регресија може трајати сатима, што чини CI/CD цевовод непрактичним.
Друга стратегија — Selective Regression. Покрећу се само тестови повезани са измењеним кодом. За одређивање веза користи се граф зависности кода. Алати: Bazel (Google), Nx (JavaScript), sbt (Scala). Selective regression штеди 60–80% времена покретања, али захтева прецизно изграђивање графа зависности — грешке доводе до пропуштених регресија.
Трећа стратегија — Prioritized Regression. Сви тестови се рангирају по приоритету: critical path (најважнији кориснички сценарији), high risk (код са историјом багова), changed code (код захваћен променом). Прво се покрећу најприоритетнији тестови — ако прођу, програмер добија брзу повратну информацију. Временски ограничено покретање: за 10 минута проверавају се критични тестови, остали — у позадини.
Први и најважнији корак — култура писања тестова. Сваку промену треба да прати тест који проверава да промена ради и тест који проверава да ништа није покварено. TDD (Test-Driven Development) даје најбоље резултате: програмер прво пише тест који пада, затим код који га пролази. Ово гарантује да тест постоји пре кода.
Други корак — CI/CD цевовод са обавезним покретањем тестова. Pull request не може бити спојен док сви тестови не прођу. Не могу се „прескочити“ тестови због хитности — хитне промене пролазе убрзани, али обавезни скуп тестова. Према Google DevOps Research, тимови са обавезним CI/CD имају 3 пута мање регресија у продукцији.
Трећи корак — мониторинг у продукцији. Чак и најбољи тестови не гарантују 100% заштиту од регресија. Алати за observability (Sentry, Datadog, New Relic) треба да прате кључне метрике након сваког постављања: стопу грешака, латенцију, пропусност. Аутоматски повратак (rollback) при прекорачењу прагова — сигурносни јастук ако регресија ипак доспе у продукцију.
Четврти корак — code review са регресионим размишљањем. Прегледач треба да постави питање: „Који још модули могу бити покварени овом променом?“. Није довољно проверити да је код исправан — треба проверити да неће нарушити суседну функционалност. Контролна листа за code review треба да садржи ставку „провера регресија у суседним модулима“.
Често постављана питања
Регресија — је баг којег није било раније. Обичан баг је могао постојати од тренутка креирања функције. Регресија је увек везана за конкретну промену — то омогућава коришћење git bisect за проналажење узрока.
Користи git bisect: наведи комит где је све радило и комит где се покварило. Git ће извршити бинарну претрагу историје и пронаћи комит који је изазвао регресију. Ово ради чак и за велике пројекте са хиљадама комитова.
Нема тачног броја, али постоји емпиријско правило: покривеност кључних корисничких токова треба да буде 100%, покривеност свих функција — најмање 70%. Квалитет је важнији од количине: тест који проверава edge case вреди више од десет тестова на happy path-у.
Да, и то се назива infrastructure regression. Ажурирање оперативног система, верзије базе података, SSL сертификата или конфигурације веб сервера може покварити код који ради. IaC (Infrastructure as Code) и тестирање инфраструктуре (Test Kitchen, Terratest) помажу у хватању оваквих регресија.
Почни са једним критичним корисничким током. Напиши аутоматски тест за најважнији сценарио (пријава, наручивање). Покажи на демо-у како тест хвата регресију. Када тим види корист — уводи тестирање постепено, проширујући покривеност.
Завршни закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође