kehitys sykli määritelty, tdd

This commit is contained in:
moilanik
2026-06-08 09:34:28 +03:00
parent 990a645fc4
commit 2f1e983c9d
2 changed files with 311 additions and 0 deletions
+295
View File
@@ -0,0 +1,295 @@
# 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
1. **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.
2. **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.
2. **Cucumber first.** Jokaisessa tiketissä Gherkin-skenaario(t) kirjoitetaan ennen toteutusta. Skenaario ajetaan mock-kerrosta vasten → Red. Toteutuksen jälkeen → Green.
3. **TDD sisemmällä tasolla.** Cucumber-skenaarion alla: Bats-yksikkötesti → Red → toteutus → Green → refactor. Molemmat tasot vihreitä = tiketti valmis.
4. **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. |
5. **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ä.
6. **Dogfood.** Kirjaston oma `.gitea/workflows/ci.yml` kä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
```bash
# 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
```gherkin
# ❌ 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:
```bash
# ❌ 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
- `@wip` korvaa 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-script` tms.
Parempi, kuratoitu konteksti = parempi suoritus. Kontekstia ei haeta "kaikkea" — vain se mitä tämä tiketti tarvitsee.