Dio 1 – Git, pull request, CI
Dio 2 – Testovi, CD, kontejneri
Dopunska praksa – GitHub Actions
Materijale priredio Erkan Islamović
erkan.islamovic@eubd.edu.ba · erkan.islamovic@gmail.com
Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas: poglavlje 5 (označeno). Isti primjer kroz knjigu: DeliveryApp 2.0.
| Pred. | Dio 1 | Dio 2 | Praksa |
|---|---|---|---|
| 1 | Projekat i proizvod, trendovi, etika | Inženjerski pristup, alati, automatizacija | Repozitorij i vizija proizvoda |
| 2 | Modeli, uloge, planiranje | Rizici, tim, kvalitet, poboljšanje procesa | Plan izdanja, registar rizika |
| 3 | Konceptualni model, UML, SOLID | Prototip, UX, specifikacija dizajna | Wireframe i SDD |
| 4 | IDE, organizacija koda, OOP, okviri | Podaci, MVC/MVVM, čisti kod, refaktoring | Refaktoring funkcije |
| 5 | Git, pull request, CI | Testovi, CD, kontejneri | GitHub Actions |
Deset predavanja: dva časa teorije i jedan čas dopunske prakse. Danas je poglavlje 5. Isti primjer kroz knjigu: DeliveryApp 2.0.
| Pred. | Dio 1 | Dio 2 | Praksa |
|---|---|---|---|
| 6 | Klasifikacija, mjerljivost | Taktike i redizajn | Mjerenje i keš |
| 7 | Odluke, pogledi, stilovi | Troslojna i čista arhitektura, predlošci | ADR i C4 |
| 8 | CBSE, odlike, interfejsi | Principi, smjernice, provođenje | Paket sa verzijom |
| 9 | SaaS, SOA, IoT, proizvodne linije | Aspekti, reaktivno programiranje | Dekorator i tok događaja |
| 10 | Vrijednost, troškovi, razvoj ili kupnja, TCO | Metrike, procjena, NPV, povrat | NPV u Pythonu |
U petak, 19. jula 2024, u 04:09 po svjetskom vremenu (UTC), firma CrowdStrike poslala je malo ažuriranje svom sigurnosnom programu Falcon, koji štiti računare velikih firmi od napada. Nije to bila nova verzija programa, nego običan fajl s podacima, takozvani Channel File 291, s pravilima za prepoznavanje novih napada. Fajl je imao grešku koju automatska provjera nije uhvatila, a Falcon radi duboko u jezgru Windowsa. Računari koji su ga primili pali su u plavi ekran i nisu se mogli ponovo pokrenuti.
CrowdStrike je izmjenu povukao u 05:27, sat i osamnaest minuta kasnije, ali šteta je bila napravljena: Microsoft je procijenio da je pogođeno oko 8,5 miliona Windows uređaja, manje od jedan posto svih, ali među njima su bili računari aviokompanija, aerodroma, bolnica, banaka i televizija. Popravka je išla ručno, računar po računar, često uz fizički pristup mašini. U svom izvještaju CrowdStrike je obećao ono što ovo poglavlje uči: bolje automatsko testiranje i postepeno puštanje izmjena, prvo na malom broju računara, pa tek onda na svim.
U poglavlju 4 napisali smo čist kod i refaktorisali obračun uz 48 testova. Sada pitanje glasi: kako šest ljudi mijenja isti kod svaki dan, a da se izmjene ne sudaraju, i kako izmjena stiže do korisnika brzo, ali bez straha? Prvi dio govori o Gitu u timu, pull requestu i pregledu koda, kontinuiranoj integraciji i statičkoj analizi. Drugi dio govori o osiguranju kvaliteta, automatiziranim testovima, kontinuiranoj isporuci i kontejnerima.
Nakon poglavlja možete:
raditi u Gitu s granama i objasniti GitHub flow; napisati dobru commit poruku; otvoriti pull request i pregledati tuđi kod; objasniti kontinuiranu integraciju i napisati jednostavan GitHub Actions tok; protumačiti izlaz lintera; razlikovati osiguranje kvaliteta i testiranje; nacrtati piramidu testova i napisati jedinični test u pytestu i xUnitu; objasniti kontinuiranu isporuku, kanarinca, prekidač funkcije i povratak na staru verziju; objasniti šta je kontejner; nabrojati četiri DORA metrike.
| Čas | Sadržaj | Na času |
|---|---|---|
| Teorija 1 | 5.1–5.4 Git u timu, pull request, CI, statička analiza | Šta ruff kaže o staroj funkciji obracun |
| Teorija 2 | 5.5–5.8 Kvalitet, testovi, isporuka, kontejneri | Da je CrowdStrike imao kanarinca… |
| Dopunska praksa | GitHub Actions: ruff + pytest na svaki pull request | Zelena kvačica na PR-u |
Git pamti svaku izmjenu koda: ko je promijenio, šta, kada i zašto. To već znate iz ranijih predmeta. Ono što se mijenja u timu je kako šest ljudi radi istovremeno.
Odgovor su grane. Glavna grana (main) uvijek sadrži kod koji radi. Ko počinje novi posao, napravi svoju granu (tarife-po-gradu), radi u njoj koliko treba, i kad je gotov, traži da se grana spoji u main. Dok radi, ne smeta nikome, i niko ne smeta njemu.
git switch -c tarife-po-gradu # nova grana
# ... izmjene ...
git add dostava.py test_dostava.py
git commit -m "Tarifa kao podatak; dodati Grad B i C"
git push -u origin tarife-po-gradu # grana ide na GitHubTim DeliveryApp koristi jednostavan dogovor zvani GitHub flow: main je uvijek ispravan; svaki posao je kratka grana; spajanje samo preko pull requesta; nakon spajanja grana se briše. Neki timovi idu i dalje i rade trunk-based development: grane žive najviše dan-dva, a nedovršene funkcije se sakrivaju prekidačima (5.7). Što grana duže živi, to je teže spajanje.

Dobra commit poruka je kratka rečenica u zapovjednom načinu koja kaže šta i, ako nije očito, zašto: „Dodaj tarifu za Grad B”, a ne „izmjene” ili „fix”. Za godinu dana, kad neko traži zašto je dostava u Gradu B 0,60 € po kilometru, istorija Gita mora odgovoriti.
Konflikt nastaje kad dvoje promijeni istu liniju u različitim granama. Git tada ne pogađa, nego pita čovjeka. Konflikata je manje kad su grane kratke i kad se često spaja.
main je uvijek ispravan.Pull request je zahtjev: „Ovdje su moje izmjene, molim pregled prije spajanja u main.” Na GitHubu se vidi tačno šta je promijenjeno (crveno obrisano, zeleno dodato), i kolege mogu komentarisati svaku liniju.
Pregled koda ima tri cilja: naći greške dok su jeftine, širiti znanje (sada dvoje zna kako radi tarifa, a ne jedan; sjetite se rizika RZ-2), i držati kod dosljednim. Pravila koja tim DeliveryApp koristi:
Sara je poslala PR „Dodaj tarifu Grada B” sa 12 linija koda i bez ijednog testa. Šta biste napisali u komentaru, da bude korisno i ljubazno?
main.U starim projektima programeri su mjesecima radili odvojeno, pa su na kraju danima „integrisali”: spajali kod koji se nije slagao. Kontinuirana integracija (CI) okreće priču: integriše se stalno, više puta dnevno, i svaka integracija se automatski provjerava. Pojam je uveo Grady Booch 1991, a proširio ga Extreme Programming krajem devedesetih.
Analogija: CI je kao vaga i kontrola na ulazu u skladište. Svaka pošiljka (izmjena) prolazi kontrolu odmah po dolasku, a ne jednom godišnje na inventuri.
Svaki put kad neko pošalje izmjenu ili otvori pull request, server za CI:
Za DeliveryApp 2.0 to radi GitHub Actions. Tok se opisuje u YAML fajlu u repozitoriju (primjeri/ci/ci.yml):
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.13"
- run: pip install pytest ruff
- run: ruff check .
- run: pytest -q
csharp:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"
- run: dotnet test DeliveryAppTestoviDva posla (python i csharp) rade paralelno, svaki na svom virtuelnom računaru. Ako bilo koji korak padne, pull request dobija crveni krstić i, uz zaštitu grane, ne može se spojiti.

„Imamo CI server, znači da radimo kontinuiranu integraciju.” CI je navika, a ne alat: svi spajaju u main bar jednom dnevno, i kad provjera padne, popravka je prvi posao tima. Server koji svaki dan prijavljuje crveno, a niko ne reaguje, samo troši struju.
Statička analiza pregleda kod bez pokretanja. Najjednostavniji alati (linteri) traže sumnjive konstrukcije i kršenje stila; ozbiljniji traže sigurnosne propuste i zavisnosti s poznatim ranjivostima.
Kad se linter ruff pokrene nad starom funkcijom obracun iz poglavlja 4, prijavi:
PLR1730 [*] Replace `if` statement with `p = min(p, 500)`
--> primjeri\python\obracun_prije.py:44:9
|
42 | if k == "STUDENT10":
43 | p = round(t * 10 / 100)
44 | / if p > 500:
45 | | p = 500
| |___________________^Ruff je uočio isto što i mi u refaktoringu: ograničenje popusta se jednostavnije piše kao min(...). Zvjezdica [*] znači da alat može sam popraviti (ruff check --fix). Nad refaktorisanim kodom ruff ne prijavljuje ništa.
| Alat | Jezik | Šta provjerava |
|---|---|---|
| ruff, pylint, mypy | Python | stil, sumnjiv kod, tipovi |
Roslyn analizatori, dotnet format | C# | stil, sumnjiv kod (ugrađeno u prevodilac) |
| SonarQube / SonarCloud | više jezika | kvalitet, dupliran kod, složenost, „vrata kvaliteta” |
| CodeQL, Dependabot | više jezika | sigurnosni propusti, ranjive zavisnosti |
| radon | Python | ciklomatska složenost (poglavlje 4) |
Vrata kvaliteta (quality gate) su pravilo u CI-ju: PR se ne spaja ako, na primjer, novi kod nema testove ili ima funkciju složenosti veće od 10.
obracun našao isto što i refaktoring.main u GitHub flowu? Kod koji uvijek radi.[*] u izlazu ruffa? Da alat može sam popraviti problem.Odgovori: tipka R ili klik na zamućeno polje.
Osiguranje kvaliteta (QA, quality assurance) nije isto što i testiranje. Testiranje je jedna aktivnost: pokrenemo program i provjerimo rezultat. QA je sve što tim radi da bi kvalitet bio ugrađen: jasni zahtjevi s kriterijima prihvatanja, pregled dizajna, pregled koda, statička analiza, testovi, mjerenje grešaka u produkciji i retrospektive.
Dva pitanja treba razlikovati:
Testovi uglavnom verifikuju; validaciju rade prototipi i razgovor s korisnicima (poglavlje 3). Detaljno o testiranju govori udžbenik Testiranje softvera; ovdje gledamo kako se testovi uklapaju u proces isporuke.
Testovi se razlikuju po tome koliko sistema obuhvataju:
| Nivo | Šta provjerava | Brzina | Primjer u DeliveryApp |
|---|---|---|---|
| jedinični | jednu funkciju ili klasu | milisekunde | Tarifa.dostava(4.2, 2150) == 380 |
| integracioni | više dijelova zajedno (kod + baza, kod + API) | sekunde | narudžba se sačuva u bazu i pročita isto |
| sistemski / E2E | cijeli sistem kroz sučelje | sekunde do minute | robot u pregledniku naruči i vidi „Potvrdi 24,50 €” |
Mike Cohn je 2009. predložio piramidu testova: mnogo brzih jediničnih testova u osnovi, manje integracionih u sredini, i samo nekoliko sporih testova cijelog sistema na vrhu. Razlog je jednostavan: brzi testovi se pokreću na svaku izmjenu i odmah pokazuju gdje je greška, a spori testovi kroz sučelje su krhki i dugo traju.

Jedinični test u Pythonu (pytest, primjeri/python/test_obracun.py):
@pytest.mark.parametrize("grad, ocekivano", [("A", 2450), ("B", 2530), ("C", 2500)])
def test_n1001_u_tri_grada(grad, ocekivano):
assert ukupno_za_naplatu(N1001, 4.2, grad) == ocekivano
def test_izvan_zone_u_gradu_b():
with pytest.raises(ValueError):
ukupno_za_naplatu(N1001, 8.1, "B")Isto u C# (xUnit, primjeri/csharp/DeliveryAppTestovi/TarifaTestovi.cs):
public class TarifaTestovi
{
static readonly Tarifa GradB = new("B", Osnovna: 200, UkljucenoKm: 2, PoKm: 60, MaxKm: 8);
[Theory]
[InlineData(2.0, 200)] // tačno uključeni kilometri
[InlineData(2.1, 260)] // započet jedan km
[InlineData(4.2, 380)] // N-1001
[InlineData(8.0, 560)] // najdalje
public void DostavaUGraduB(double km, int ocekivano)
{
Assert.Equal(ocekivano, GradB.Dostava(km, 2150));
}
[Fact]
public void IzvanZoneSeOdbija()
{
Assert.Throws<ArgumentException>(() => GradB.Dostava(8.1, 2150));
}
}pytest u folderu primjeri/python pokrene 48 testova, a dotnet test u projektu DeliveryAppTestovi 5 testova; svi prolaze za manje od sekunde. Primijetite izbor podataka: granice (2,0 km i 2,1 km; 8,0 km i 8,1 km) i poznati primjer (N-1001). Greške najčešće žive na granicama.
Razvoj vođen testovima (TDD, test-driven development) okreće redoslijed: prvo se napiše test koji pada, pa najmanje koda da prođe, pa refaktoring. Tako svaki red koda ima razlog i test.
Kontinuirana isporuka (CD, continuous delivery) znači da je svaka izmjena koja prođe CI spremna za puštanje u rad, jednim klikom. Kontinuirano puštanje (continuous deployment) ide korak dalje: izmjena koja prođe sve provjere ide korisnicima automatski, bez klika.
Najvažnija lekcija iz priče o CrowdStrikeu nije „testirajte više”, nego „ne puštajte sve odjednom”. Tehnike koje ograničavaju štetu:

U izvještaju nakon incidenta od 19. jula 2024. CrowdStrike je naveo da su ažuriranja sadržaja (kao Channel File 291) do tada išla svim korisnicima istovremeno, i najavio postepeno puštanje, s praćenjem i mogućnošću da kupci sami biraju kada primaju ažuriranja. Jedan fajl s podacima, a ne program, srušio je milione računara: i podaci i podešavanja su dio isporuke i zaslužuju istu pažnju kao kod.
„Kod mene radi” je najpoznatija rečenica u softveru. Program radi na Sarinom računaru, ali ne na serveru, jer server ima drugu verziju Pythona ili nema neku biblioteku. Kontejner rješava problem tako što zapakuje program zajedno sa svim što mu treba: verzijom jezika, bibliotekama i podešavanjima. Analogija je brodski kontejner: luka ne mora znati šta je unutra, svi kontejneri imaju iste dimenzije i isto se prenose.
Najpoznatiji alat je Docker (od 2013). Recept za kontejner je Dockerfile (primjeri/ci/Dockerfile):
FROM python:3.13-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY deliveryapp/ deliveryapp/
EXPOSE 8000
CMD ["uvicorn", "deliveryapp.api:app", "--host", "0.0.0.0", "--port", "8000"]CI nakon uspješnih testova napravi sliku kontejnera (docker build), označi je verzijom (npr. deliveryapp-api:2.0.0) i pošalje u registar. Ista slika ide na test, pa na kanarinca, pa svima. Za mnogo kontejnera na mnogo servera koristi se orkestrator, najčešće Kubernetes (poglavlje 6, skaliranje).
Kako znati da li tim isporučuje dobro? Istraživanje opisano u knjizi Accelerate (2018) predlaže četiri DORA metrike:
| Metrika | Pitanje | DeliveryApp 1.x | Cilj za 2.0 |
|---|---|---|---|
| učestalost isporuke | koliko često puštamo u rad? | jednom mjesečno | više puta sedmično |
| vrijeme od izmjene do produkcije | koliko traje put izmjene? | dvije sedmice | manje od jednog dana |
| procenat neuspjelih izmjena | koliko isporuka izazove problem? | oko 20 % | ispod 10 % |
| vrijeme oporavka | koliko brzo popravimo kvar? | pola dana | ispod jednog sata |
Važan nalaz istraživanja: najbolji timovi su i brži i stabilniji. Brzina i stabilnost ne isključuju jedna drugu, jer male, česte i automatski provjerene izmjene manje kvare i lakše se vraćaju.
Odgovori: tipka R ili klik na zamućeno polje.
Cilj je da svaki pull request u repozitoriju deliveryapp-2 automatski prođe linter i testove. Trajanje: jedan čas.
U repozitorij kopirajte dostava.py, obracun_poslije.py, obracun_prije.py i test_obracun.py iz primjeri/python. Lokalno pokrenite:
python -m pytest -q
python -m ruff check .Ruff će prijaviti obracun_prije.py. Ispravite ga sa ruff check --fix ili ga obrišite (više nam ne treba, jer testovi već pokrivaju novu funkciju).
Napravite fajl .github/workflows/ci.yml s dijelom python iz odjeljka 5.3 (dio csharp dodajte ako ste kopirali i projekat DeliveryAppTestovi). Commit i push u main. Na kartici Actions pratite izvršavanje.
pokvari-tarifu.dostava.py promijenite po_km=60 za Grad B u po_km=50.test_n1001_u_tri_grada[B]).U Settings → Branches (ili Rulesets) uključite pravilo za main: spajanje samo preko PR-a, uz uspješnu provjeru CI. Pokušajte spojiti pokvareni PR. Zatim vratite ispravku i posmatrajte zelenu kvačicu.
Razmijenite PR s kolegom i ostavite bar dva komentara po pravilima iz 5.2.
Rezultat: link na PR koji je pao pa prošao CI, s komentarima pregleda.
Rješenja su sakrivena – tipka R ili klik na okvir.
Prepravite poruke: „izmjene”, „fix”, „radi sad”, „Sara-final-final2”.
Na primjer: „Dodaj tarifu za Grad C”; „Ispravi zaokruživanje kilometara u Gradu B (ceil umjesto int)”; „Vrati 404 kad narudžba ne postoji”; „Dodaj ekran Moje narudžbe s dugmetom Ponovi”. Poruka kaže šta je urađeno i, gdje nije očito, zašto.
Napišite tri testa za tarifu Grada C (2,50 € do 3 km, 0,50 € po započetom km, najdalje 12 km) koji provjeravaju granice.
3,0 km → 250 centi; 3,1 km → 300 centi; 12,0 km → 250 + 9 × 50 = 700 centi; 12,1 km → greška „izvan zone”. Uz to iznos 30,00 € na bilo kojoj udaljenosti → 0.
Tim DeliveryApp ima 10 jediničnih, 20 integracionih i 60 testova kroz preglednik, i CI traje 40 minuta. Šta nije u redu i šta predlažete?
Piramida je naopako (sladoled-kornet): najviše je sporih i krhkih testova kroz preglednik. Prijedlog: pravila (dostava, popust, stanja) pokriti jediničnim testovima (stotine, ukupno nekoliko sekundi), zadržati integracione za bazu i API, a testove kroz preglednik svesti na nekoliko glavnih tokova (naruči, ponovi, otkaži).
Opišite plan puštanja funkcije „Praćenje dostavljača uživo” u rad, koristeći prekidač funkcije i kanarinca. Šta pratite i kada odustajete?
(1) Kod ide u produkciju isključen. (2) Prekidač se uključi za tim i nekoliko dostavljača (interni test). (3) Uključi se za 5 % kupaca Grada C na dva dana; prate se greške aplikacije, vrijeme odziva i potrošnja baterije na mobitelu. (4) Ako je sve u granicama (npr. bez rasta grešaka, odziv do 2 s), 25 %, pa 100 % i drugi gradovi. (5) Ako greške porastu ili odziv pređe 2 s, prekidač se isključuje odmah, bez nove isporuke.
main samo preko pull requesta s pregledom koda, malim izmjenama i komentarima o kodu, ne o osobi.