13 KiB
Test-Driven Development Guide — Gitea Actions CI -kirjasto
Updated: 2026-06-08
Tämä dokumentti kuvaa testivetoisen kehityksen menetelmän, testiarkkitehtuurin ja kehitysloopin tässä projektissa.
Kolmikerroksinen testiarkkitehtuuri
┌──────────────────────────────────────────────────────────────────────┐
│ LAYER 1: Cucumber acceptance / E2E │
│ Cucumber + Gherkin | tests/features/<NNNN>-<name>.feature │
│ Yksi feature file per tiketti. Käyttäjän näkökulma. │
│ Tägätty: @mock, @real, @ticket-NNNN │
│ Tiketti on valmis kun @mock-skenaariot GREEN │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 2: Workflow-validointi │
│ Bats | tests/workflows.bats │
│ YAML-skeema, input/output -kontrakti │
│ Käytetään kun tiketti koskee reusable workflow'ta │
├──────────────────────────────────────────────────────────────────────┤
│ LAYER 3: Bash-yksikkötestit │
│ Bats | tests/<script>.bats │
│ Red-Green-Refactor per skripti │
│ Käytetään kun tiketti koskee bash-skriptiä │
└──────────────────────────────────────────────────────────────────────┘
Periaatteet
-
Feature file on kova portti, kun tiketti sellaisen vaatii. Tiketti itse kertoo, liittyykö siihen feature file. Jos liittyy ja tiedosto puuttuu → virhe, ei jatketa. Kaikki tiketit eivät tuo käyttäjälle näkyviä ominaisuuksia — teknisille tiketeille (siivous, validointi, konfiguraatio) feature fileä ei vaadita.
-
Feature file on PO-roolin omistama. Toteuttaja ei kirjoita omaa feature fileään — se olisi itsensä valvomista. Feature file kirjoitetaan PO-toimesta ennen toteutusta. Ainoa sallittu muutos toteutusvaiheessa:
@wip-tagin poistaminen skenaariolta, kun se on toteutettu ja menee vihreäksi. -
Cucumber first. Jokaisessa tiketissä Gherkin-skenaario(t) kirjoitetaan ennen toteutusta. Skenaario ajetaan mock-kerrosta vasten → Red. Toteutuksen jälkeen → Green.
-
TDD sisemmällä tasolla. Cucumber-skenaarion alla: Bats-yksikkötesti → Red → toteutus → Green → refactor. Molemmat tasot vihreitä = tiketti valmis.
-
Tägätty ympäristösopivuus. Skenaariot tägätään sen mukaan, missä ympäristöissä ne voidaan ajaa. Kaksi ympäristöä: paikallinen mock-kerros (
@mock) ja oikea testiympäristö (@real). Useimmat skenaariot ajetaan molemmissa. Osa skenaarioista toimii vain toisessa — esimerkiksi OIDC-kirjautuminen vaatii oikean Traefikin (@real), kun taas mock-spesifi edge case toimii vain paikallisesti (@mock).
| Tagi | Ympäristö | Kuvaus |
|---|---|---|
@mock |
Paikallinen mock | tests/helpers/mock-api.sh — Gitea + MinIO mockit |
@real |
Oikea testiympäristö | Todellinen Gitea-instanssi + MinIO-klusteri |
@ticket-NNNN |
— | Skenaariotason tagi. Kertoo minkä tiketin osana skenaario luotiin. Mahdollistaa konfliktinratkaisun: kun vanha skenaario hajoaa, tiedetään mihin tikettiin se kuuluu. |
@wip |
— | Work in progress — PO:n kirjoittama skenaario jota ei ole vielä toteutettu. CI skipaa (--tags ~@wip). Toteuttaja poistaa tämän tagin kun skenaario menee vihreäksi. |
-
Feature filet scopen mukaan. Käyttäjätoiminnallisuustiketti aloittaa oman feature fileen. Myöhemmät tiketit voivat lisätä skenaarioita samaan tiedostoon, jos scope on sama — skenaariot niputetaan aihealueen, ei tiketin mukaan. Jokainen skenaario tägätään
@ticket-NNNN:lla, joten omistajuus säilyy vaikka tiedostossa on usean tiketin skenaarioita. Tekniset tiketit (skriptit, siivous, validointi) eivät tarvitse feature fileä — ne käyttävät Bats-testejä. -
Dogfood. Kirjaston oma
.gitea/workflows/ci.ymlkäyttää kirjaston omia reusable workflow'ita. Jokainen push testaa kirjaston itse itsellään.
Kehityslooppi
Kun tiketillä on feature file:
for each scenario in feature file:
1. Valitse skenaario jossa `@wip`-tagi
→ cucumber <feature> --tags @ticket-NNNN and @wip
→ RED
2. TDD-sykli
a. Bats-yksikkötesti → RED
b. Toteuta minimi → Bats GREEN
c. Cucumber → GREEN
d. Poista `@wip`-tagi skenaariolta
e. Refactor → Bats + Cucumber pysyvät GREEN
3. Seuraava `@wip`-skenaario
until no @wip scenarios remain
Kun tiketillä ei ole feature fileä (tekninen tiketti), käytetään pelkkää Bats-TDD-sykliä:
1. Bats-yksikkötesti → RED
2. Toteuta minimi → Bats GREEN
3. Refactor → Bats GREEN
DoD (Definition of Done)
Tiketille jolla on feature file:
- [ ] Cucumber: @ticket-NNNN and @mock → kaikki skenaariot GREEN
- [ ] Bats: tiketin .bats-testit GREEN
- [ ] Toteutus: skripti / workflow olemassa
- [ ] Refactor: ei duplikaatiota, virheenkäsittely kunnossa
Tekniselle tiketille (ei feature fileä):
- [ ] Bats: tiketin .bats-testit GREEN
- [ ] Toteutus: skripti / workflow olemassa
- [ ] Refactor: ei duplikaatiota, virheenkäsittely kunnossa
Step definitionit ja mock-kerros
Cucumber step definition (*.steps.sh)
│
├── asettaa ympäristön: source tests/helpers/mock-api.sh
│ GITEA_API_URL, MINIO_BASE_URL, tokenit, GITHUB_*
│
├── kutsuu skriptiä: bash scripts/<script>.sh <args>
│
└── tarkistaa tuloksen: exit code, stdout, mock-lokit
Mock (tests/helpers/mock-api.sh) on jaettu Bats- ja Cucumber-testien välillä. Step definitionit kutsuvat skriptejä suoraan — ei HTTP-kutsuja cucumberista ulospäin.
Mock-infrastruktuuri
Mock on puhdas bash — ei Dockeria, ei WireMockia.
| Mock | Portti | Rajapinta |
|---|---|---|
| Gitea REST API | 18080 |
/api/v1/repos/*/statuses/*, /dispatches, /runs, /tags, /commits/* |
| MinIO S3 API | 19000 |
mc cp, mc ls, mc rm |
Esivaatimukset
| Työkalu | Versio | Asennus |
|---|---|---|
| Bats | 1.11+ | brew install bats |
| Cucumber | latest | npm install -g @cucumber/cucumber |
| jq | 1.6+ | brew install jq |
| yq | 4+ | brew install yq |
| mc (MinIO client) | latest | brew install minio/stable/mc |
Tiedostorakenne
tests/
├── features/
│ ├── <NNNN>-<name>.feature # Yksi per tiketti
│ └── step_definitions/
│ ├── common.steps.sh # Jaetut (Given API, Given MinIO…)
│ └── <NNNN>-<name>.steps.sh # Yksi per tiketti
├── helpers/
│ └── mock-api.sh # Jaettu mock-palvelin
├── <script>.bats # Yksikkötestit per skripti
└── workflows.bats # Workflow-validointi
Testien ajaminen
# Kehitys: tiketin mock-skenaariot (vain @wip, eli toteuttamattomat)
cucumber tests/features/<NNNN>-<name>.feature --tags @mock and @wip
# Kehitys: kaikki tiketin mock-skenaariot (poislukien @wip)
cucumber tests/features/<NNNN>-<name>.feature --tags @mock and ~@wip
# Tikettikohtainen tagi (poislukien @wip)
cucumber tests/features/ --tags @ticket-NNNN and ~@wip
# Kaikki mock-skenaariot (poislukien @wip)
cucumber tests/features/ --tags @mock and ~@wip
# CI-putki: kaikki valmiit skenaariot
cucumber tests/features/ --tags ~@wip
# Oikea ympäristö (CI-putki)
cucumber tests/features/ --tags @real
# Bats-yksikkötestit
bats tests/<script>.bats
# Kaikki Bats-testit
bats tests/
# Workflow-validointi
bats tests/workflows.bats
Living Documentation
Gherkin-skenaariot ovat ensisijaisesti elävää dokumentaatiota ja vasta toissijaisesti testejä. Dokumentaatio kuvaa järjestelmän käyttäytymisen — testit varmistavat, että dokumentaatio pitää paikkansa.
Konfliktinratkaisu @ticket-NNNN-tagien avulla
Kun uusi tiketti muuttaa järjestelmän käyttäytymistä, vanhat skenaariot voivat mennä rikki:
1. Tiketti 0013 toteutettu, uudet skenaariot @ticket-0013
2. Push → CI ajaa cucumber --tags ~@wip
3. Punaista: vanha @ticket-0001-skenaario feilaa
4. Tiimi näkee ristiriidan: @ticket-0013 vs @ticket-0001
5. Tiimi ratkaisee: kumpi skenaario kuvaa oikeaa käyttäytymistä?
→ Jos uusi on oikein → vanha skenaario päivitetään
→ Jos vanha kuvaa toista tapausta → uutta skenaariota tarkennetaan
6. Konflikti ratkaistu → green → PR → master
@ticket-NNNN mahdollistaa sen, että tiedetään kuka skenaarion omistaa — ilman attribuutiota ristiriitatilanteessa ei voida jäljittää mikä muutos aiheutti ongelman.
Test code = production code
Testikoodiin pätevät samat periaatteet kuin tuotantokoodiin. Huono testiarkkitehtuuri tekee ylläpidosta raskasta — testien on oltava helppoja lukea, muuttaa ja laajentaa.
1. Dependency rule: sisäänpäin
Gherkin-featuret (bisneskieli, STABIILI)
↓ riippuu
Step definitionit (käännös bisnes → tekninen)
↓ riippuu
Mock / Bats (infrastruktuuri, muuttuu teknologian mukana)
Feature ei koskaan viittaa mockiin, porttiin tai tiedostopolkuun. Step definition kääntää. Sama periaate kuin Entities → Use Cases → Adapters.
2. SLAB: yksi abstraktiotaso per kerros
# ❌ Matala ja korkea sekaisin
Scenario: POST /api/v1/repos/statuses returns 201
# ✅ Vain bisneskieltä
Scenario: Developer sees build status on commit
Gherkin = mitä. Step definition = miten. Ei teknisiä yksityiskohtia feature-tasolla.
3. Single responsibility per scenario
Yksi When, yksi lopputulos. Jos skenaariossa on kaksi When-lausetta → pilko kahdeksi skenaarioksi. Scenario Outline + Examples kun sama logiikka eri datalla.
4. Guard clauses step definitioneissa
Tarkista esiehdot heti, palauta early. Ei syviä if-puita:
# ❌ Deep nesting
if [ -n "$GITEA_TOKEN" ]; then
if [ -n "$GITEA_API_URL" ]; then
curl ...
fi
fi
# ✅ Guard clause
[ -z "$GITEA_TOKEN" ] && echo "Missing GITEA_TOKEN" && exit 1
[ -z "$GITEA_API_URL" ] && echo "Missing GITEA_API_URL" && exit 1
curl ...
5. Ei kuollutta koodia
@wipkorvaa kommentoidut skenaariot — ei#-rivejä, ei/* TODO */- Git muistaa poistetun koodin — älä jätä sitä tiedostoon
- Step definitioneissa ei kuolleita polkuja tai käyttämättömiä parametreja
6. Tiedostorakenne huutaa tarkoitusta
tests/features/
├── commit-status.feature ← "tämä on commit-status-järjestelmä"
├── report-browsing.feature ← ei "http-mock" tai "api-wrapper"
└── step_definitions/
├── commit-status.steps.sh
└── common.steps.sh ← jaetut, ei "utils" tai "helpers"
Tiedostonimi kertoo mitä testataan, ei miten. Ei kansioita kuten unit/, integration/ — ne kertovat testaustavasta, eivät järjestelmästä.
7. Testit ilman infraa — ja infraa vasten
@mock-tagi varmistaa että testit ajetaan paikallisesti ilman oikeaa Gitea-instanssia. Sisempi testikerros ei koskaan riipu ulkoisista palveluista — sama periaate kuin Entities-testit ilman tietokantaa.
Sama testi toimii molemmissa ympäristöissä:
@mock → nopea paikallinen palaute, ei ulkoisia riippuvuuksia
@real → varmistaa että mock-kerros on oikein
Jos skenaario menee vihreäksi @mock:lla mutta punaiseksi @real:lla, mock on epätarkka — se ei mallinna oikeaa järjestelmää oikein. @real-testit validoivat mockin. Ne sulkevat silmukan: mock mahdollistaa nopean kehityksen, @real varmistaa ettei mock valehtele.
Kontekstikuratoinnin periaate
Jokaisen tiketin suunnittelu, Gherkin-määrittely ja toteutus tapahtuvat omissa sessioissaan. Ennen toteutusta ladataan kontekstiin:
docs/test-plan/tdd-guide.md— testiarkkitehtuuri ja TDD-menetelmä- Tiketin oma feature file — hyväksymiskriteerit
- Tarvittavat domain-skillit —
tdd,implementation,bash-scripttms.
Parempi, kuratoitu konteksti = parempi suoritus. Kontekstia ei haeta "kaikkea" — vain se mitä tämä tiketti tarvitsee.