I denne øvingen skal du:
- Generere et nytt Spring Boot-prosjekt med Spring Initializr
- Opprette et eget GitHub-repo og pushe koden dit
- Sette opp branch protection på
main - Øve på Pull Request-flyten (feature branch → PR → merge)
- Sette opp en GitHub Actions workflow som kjører unit-tester på hver PR og på hver push til
main
Målet er ikke å lære Spring Boot i dybden – vi bruker det kun som et konkret prosjekt å bygge og teste. Fokuset er på arbeidsflyten rundt kode: branching, code review, og kontinuerlig integrasjon (CI).
Underveis kommer vi til å bruke GitHub CLI (gh) til å autentisere Git og opprette repoer fra terminalen, og noen litt mer avanserte Codespaces-triks — blant annet å legge til flere rot-mapper i workspacet så du kan jobbe med to prosjekter side om side i samme editor.
Etter å ha fullført øvingen skal du kunne:
- Forklare hva CI (Continuous Integration) er og hvorfor det er nyttig
- Sette opp branch protection rules for å hindre direkte push til
main - Lage en Pull Request, be om review, og merge etter godkjenning
- Skrive en enkel GitHub Actions workflow som bygger og tester et Maven-prosjekt
- Bli komfortabel med Git på kommandolinjen —
git init,git add,git commit,git push,git checkout -b,git remote— uten å måtte støtte deg på GUI-verktøy - Bruke GitHub CLI (
gh) til å logge inn og opprette et nytt repo fra kommandolinjen (gh auth login,gh repo create) - Bruke Codespaces multi-root workspaces (
code -a) for å jobbe med flere mapper samtidig i samme editor-vindu - Forstå hva Maven Wrapper (
./mvnw) er, og hvorfor det gjør builds mer reproduserbare på tvers av maskiner og CI-systemer
Dette er en klassisk felle som kommer til å forvirre deg om du ikke er obs på det fra start. I løpet av øvingen jobber du med to helt separate GitHub-repoer, og de skal ligge i to sidestilte mapper — ikke den ene inne i den andre:
/workspaces/
├── ci-spring-boot/ ← Repo 1: din fork (bare for README + devcontainer)
└── ci-demo/ ← Repo 2: ditt nye Spring Boot-prosjekt
| # | Repo | Mappe | Hva ligger her? |
|---|---|---|---|
| 1 | Din fork (ci-spring-boot) |
/workspaces/ci-spring-boot/ |
README-en du leser nå og .devcontainer/. Ikke push endringer hit. |
| 2 | Ditt nye Spring Boot-repo (f.eks. ci-spring-boot-ola) |
/workspaces/ci-demo/ |
Selve Spring Boot-prosjektet du lager i Del 1. Det er her all koden din, PR-ene, branch protection og CI skal leve. |
Legger du Spring Boot-prosjektet inne i fork-en, går git-kommandoene lett til feil repo — git leter oppover i mappetreet og treffer fork-ens .git/ uten at du merker det. Hold repo 2 utenfor repo 1. Del 1 forteller deg hvor du skal legge det (/workspaces/ci-demo/).
pwd # Hvilken mappe står jeg i?
git remote -v # Hvilket GitHub-repo peker denne mappa på?Ser du en URL med glennbechdevops/ci-spring-boot (lærerens repo), er du i fork-en. Ser du en URL med ditt eget brukernavn og repo-navnet du valgte, er du i ditt eget repo. Ser du ingenting i git remote -v fra ci-demo/ før du har lagt til remote — det er som forventet.
En fork er din egen kopi av et repo på GitHub. Du trenger en for å kunne starte en Codespace og endre filer uten å påvirke originalen.
Klikk Fork-knappen øverst til høyre på dette repoet og velg din egen konto som destinasjon.
I din fork, velg den grønne knappen "<> Code", og "Create codespace on main".
Dette repoet har en devcontainer (se .devcontainer/devcontainer.json) som gjør at Codespaces automatisk installerer alt du trenger:
- JDK 21 (Temurin) – for å kompilere og kjøre Java-koden
- Maven – byggeverktøyet vi bruker for å bygge Spring Boot-prosjektet
- GitHub CLI (
gh) – kommandolinjeverktøy for GitHub. Vi bruker det til å autentisere Git mot GitHub og til å opprette repo direkte fra terminalen (se Del 2).
Første gang Codespacet startes tar det noen minutter å bygge miljøet.
Sjekk at alt er på plass ved å kjøre følgende i terminalen:
java -version
mvn -version
gh --versionDu skal få et versjonsnummer tilbake for alle tre. Får du "command not found" er devcontaineren ikke ferdig bygget, eller bygget feilet – sjekk loggen fra "Codespaces: View Creation Log".
Gå til Spring Initializr: https://start.spring.io og velg:
- Project: Maven
- Language: Java
- Spring Boot: siste stabile versjon (unngå SNAPSHOT/RC)
- Group:
no.kristiania - Artifact:
ci-demo - Packaging: Jar
- Java: 21 (eller den versjonen som er tilgjengelig i Codespaces)
- Dependencies:
Spring Web
Klikk Generate. Nettleseren laster ned en zip-fil som heter ci-demo.zip.
-
Drag & drop
ci-demo.zipfra nedlastings-mappa inn i Explorer-panelet i Codespace-vinduet. -
Flytt den til
/workspaces/og pakk ut:mv /workspaces/ci-spring-boot/ci-demo.zip /workspaces/ cd /workspaces unzip ci-demo.zip -d ci-demo cd ci-demo
-
Bekreft at
pom.xml,mvnwogsrc/ligger i/workspaces/ci-demo/.
Pro tips: Vil du hoppe over nettleser-runden, kan du hente prosjektet direkte i Codespaces-terminalen med
curlmot Initializr sitt API.-d-flaggene sender de samme feltene som du fyller inn i Initializr-web-en (som HTTP form data), og utenbootVersionbruker Initializr default (siste stabile).
cd /workspaces
curl https://start.spring.io/starter.zip \
-d type=maven-project -d language=java \
-d groupId=no.kristiania.cidemo -d artifactId=ci-demo \
-d name=ci-demo -d packageName=no.kristiania.cidemo \
-d javaVersion=21 -d dependencies=web \
-o ci-demo.zip
unzip ci-demo.zip -d ci-demo
cd ci-demoVS Code / Codespaces viser bare workspace-roten (fork-en) i Explorer-panelet, så /workspaces/ci-demo/ er på disk men usynlig i sidepanelet. Legg den til som ekstra rot-mappe:
code -a /workspaces/ci-demo-a er kortformen for --add — den legger til mappa i workspacet uten å bytte hovedmappe eller reloade vinduet. Nå ser du begge mappene sidestilt i Explorer, og kan åpne filer i ci-demo/ med musa som normalt.
./mvnw testDu skal få en grønn build med minst én test (contextLoads) som passerer.
Hva er
./mvnw? Det er Maven Wrapper — et lite shell-script (og.cmd-variant for Windows) som følger med prosjektet. Første gang du kjører det, laster det ned den nøyaktige Maven-versjonen prosjektet er testet med (se.mvn/wrapper/maven-wrapper.properties) og bruker den for bygget. Det betyr at alle — du lokalt, andre du samarbeider med, GitHub Actions-runnerne — bygger med samme Maven-versjon uten å måtte installere Maven manuelt../mvnwer en drop-in erstatning formvn, så alle kommandoer du kunne kjørt medmvn(test,package,verify, …) fungerer likt med./mvnw.
Før du kan pushe kode, må Git kunne bevise til GitHub at det er deg som pusher. I et helt ferskt Codespace har du ikke SSH-nøkler satt opp, og passord-innlogging over HTTPS ble deaktivert av GitHub for flere år siden. Vi bruker gh (GitHub CLI) — den er forhåndsinstallert i Codespaces og på de fleste utviklermaskiner.
NB (Codespaces): Codespace-en har allerede en
GITHUB_TOKEN-env-var satt som er scopet til fork-en.ghvil bruke den og hoppe over innlogging — men den tokenet får ikke opprette et nytt repo på kontoen din. Fjern den først:unset GITHUB_TOKEN
gh auth loginVelg:
- GitHub.com
- HTTPS som protokoll
- Yes når den spør om å bruke
ghsom Git credential helper - Login with a web browser — kopier engangskoden og lim inn i nettleseren
Etter dette har Git en credential helper som automatisk sender en gyldig token ved hver git push — du slipper å taste noe mer.
git config --global user.name "Ola Nordmann"
git config --global user.email "ola@example.com"Fra rot-mappa av Spring-prosjektet:
git init
git add .
git commit -m "Initial commit from Spring Initializr"
git branch -M main
gh repo create ci-spring-boot-ditt-navn --public --source=. --pushBytt ditt-navn med noe unikt (f.eks. ci-spring-boot-ola). Flaggene til gh repo create:
--public— repoet blir offentlig. Bruk--privatehvis du heller vil ha det privat.--source=.— bruk gjeldende mappe som kilde.ghfinner den lokale.git/-mappa og setter opp riktigorigin-remote.--push— pushmainopp til det nye repoet umiddelbart etter opprettelse.
Resultat: repoet finnes på GitHub-kontoen din, origin er satt, og main er pushet — alt i én kommando.
Vi vil hindre at noen (inkludert deg selv) pusher direkte til main uten å gå via en Pull Request.
- Gå til
Settings→Branchesi repoet ditt. - Under Branch protection rules, klikk Add rule (eller Add branch ruleset i nyere UI).
- Sett Branch name pattern til
main. - Huk av for følgende:
- Require a pull request before merging
- Require status checks to pass before merging (vi legger til selve sjekken i Del 5)
- Lagre.
Prøv å pushe direkte til main etterpå – det skal feile med en melding om at branchen er beskyttet.
-
Opprett en feature branch:
git checkout -b feature/hello-endpoint
-
Legg til en enkel REST-endpoint i prosjektet. For eksempel en ny klasse
HelloController.java:package no.kristiania.cidemo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class HelloController { @GetMapping("/hello") public String hello() { return "Hei fra CI-øvingen!"; } }
-
Skriv en enkel test for controlleren. Legg fila her:
src/test/java/no/kristiania/cidemo/HelloControllerTest.java:package no.kristiania.cidemo; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.webmvc.test.autoconfigure.WebMvcTest; import org.springframework.test.web.servlet.MockMvc; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @WebMvcTest(HelloController.class) class HelloControllerTest { @Autowired MockMvc mockMvc; @Test void helloReturnsGreeting() throws Exception { mockMvc.perform(get("/hello")) .andExpect(status().isOk()) .andExpect(content().string("Hei fra CI-øvingen!")); } }
Hva er
@WebMvcTest?Spring Boot har flere «test-slicer» — annotasjoner som starter opp bare den delen av applikasjonen du trenger for en gitt test.
@WebMvcTester slicen for web-laget:- Den starter ikke hele Spring Boot-appen (som
@SpringBootTestgjør). Ingen database, ingen service-beans, ingen fullversjons-context. - Den laster kun controlleren du peker på (
HelloController.class), pluss Spring MVC-infrastrukturen rundt (routing, JSON-serialisering, filter osv.). - Du får inn en
MockMvcsom lar deg sende falske HTTP-requests og gjøre assertions på responsen — uten å faktisk starte en Tomcat-server på en port. - Resultat: testen er rask (starter på ms, ikke sekunder) og fokusert — du tester controlleren, ikke hele appen.
Kjør testen lokalt før du pusher:
./mvnw --batch-mode test--batch-mode(kortform-B) skrur av interaktiv output og fargede tegn. Kjekt i CI og i terminaler der du vil ha kortfattet, maskin-lesbar logg. Kan droppes lokalt —./mvnw testalene fungerer også. - Den starter ikke hele Spring Boot-appen (som
-
Commit og push branchen:
git add . git commit -m "Add hello endpoint" git push -u origin feature/hello-endpoint
-
Gå til GitHub og opprett en Pull Request fra
feature/hello-endpointmotmain. -
Merge PR-en.
Vi skal lage en workflow som kjører unit-tester ved:
- hver push til
main - hver Pull Request som peker mot
main
Lag fila .github/workflows/ci.yml i repoet:
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Sjekk ut repo
uses: actions/checkout@v4
- name: Sett opp JDK 21
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '21'
cache: maven
- name: Kjør tester
run: ./mvnw --batch-mode test- Lag en ny feature branch, gjør en liten endring, og opprett en Pull Request.
- Gå til fanen Actions i repoet og se at workflowen kjører.
- På PR-en skal du nå se en grønn (eller rød) status-check ved siden av commit-en.
Gå tilbake til Settings → Branches → rulen for main:
- Under Require status checks to pass before merging, søk opp og velg
build-and-test.- Det du søker etter her er navnet på jobben i workflowen — altså nøkkelen under
jobs:ici.yml(i vårt tilfellebuild-and-test). Det er ikke navnet på workflowen (name: CI) eller navnet på et enkeltsteg. - Merk: søkefeltet finner bare status-checks som GitHub har sett minst én gang. Har workflowen aldri kjørt mot dette repoet, får du ingen treff. Kjør PR-en fra forrige seksjon først.
- (Beklager på GitHub sine vegne at denne UX-en er litt dårlig — det er ikke opplagt at det er jobb-navnet du skal søke etter, og feltet gir null hint hvis workflowen ikke har kjørt ennå.)
- Det du søker etter her er navnet på jobben i workflowen — altså nøkkelen under
- Lagre.
Nå kan ingen PR merges før testene har passert.
- Endre en test slik at den feiler, push til en feature branch, og opprett en PR.
- Bekreft at:
- Actions-workflowen slår rødt
- Merge-knappen er deaktivert på PR-en
- Rett opp testen og bekreft at PR-en nå kan merges.
Utvid ci.yml med noe av følgende:
- Kjør
./mvnw --batch-mode verifyi stedet fortest(kjører også integrasjonstester) - Legg til et steg som feiler bygget hvis kodedekningen er for lav (f.eks. med JaCoCo)
- Legg til et lint-steg med Checkstyle eller Spotless
- Kjør workflowen på flere Java-versjoner samtidig med en
matrix-strategi
Slå deg sammen med en medstudent for å øve på ekte review-flyt.
Settings→Collaborators→ Add people — inviter medstudenten din med GitHub-brukernavnet deres. De må akseptere invitasjonen fra e-post/varsel.- Skru på Require approvals: 1 i branch protection-rulen for
main(Settings→Branches). - Lag en feature branch, gjør en liten endring, push og opprett en PR.
- Legg medstudenten som Reviewer på PR-en.
- Medstudenten reviewer, kommenterer, og godkjenner. Merge PR-en.
- Bytt roller: la medstudenten opprette PR i sitt repo og la deg reviewe.