kehitys sykli määritelty, tdd
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user