Δοκιμές¶
Η πειθαρχία δοκιμών του AdaptiveLearner επιβάλλεται από το
make test σε κάθε αλλαγή. Η στρατηγική είναι πυραμίδα: unit
στη βάση, integration στη μέση, E2E smoke στην κορυφή.
Αριθμός δοκιμών¶
| Επίπεδο | Αριθμός | Εργαλείο |
|---|---|---|
| Backend unit + integration | 786 | pytest ^9 |
| Plugin tests (10 plugins) | 615 | pytest ^9 |
| Frontend unit + integration | 1233 | Vitest 4 |
| E2E smoke | 16 spec files | Playwright |
Σύνολο (make test) |
2634 |
Ανάλυση plugin: assessment 110 + ai-anthropic 34 + ai-openai 31 + ai-gemini 33 + session 215 + tracking 64 + tools 58 + gamification 23 + anki 20 + notebooklm 27.
Backend pytest¶
make test-backend # 786 tests, ~35s
cd backend && poetry run pytest -k "test_session" -v
cd backend && poetry run pytest --pdb
Τα τεστ βρίσκονται στο backend/tests/. Τα fixtures στο
conftest.py παρέχουν νέα in-memory SQLite DB ανά τεστ, το
TestClient και έναν mocked plugin manager. Η απομόνωση τεστ
είναι αυστηρή - το ADAPTIVE_LEARNER_TEST=1 ορίζεται πριν από
οποιαδήποτε εισαγωγή app.*.
Plugin tests¶
Κάθε plugin έχει τον δικό του κατάλογο tests/:
make test-plugins # όλα τα 7
make test-plugin-session # μόνο ένα
cd plugins/adaptive-learner-plugin-session && poetry run pytest
Τα plugin tests δεν φορτώνουν την εφαρμογή FastAPI - ασκούν τα
modules του plugin σε απομόνωση. Χρησιμοποίησε mock στον
pluggy.PluginManager κατά τη δοκιμή εκτέλεσης hooks.
Frontend Vitest¶
make test-frontend # 387 tests, ~2s
cd frontend && bunx vitest # watch mode
cd frontend && bunx vitest run src/storage/ # ένας κατάλογος
Τα τεστ βρίσκονται δίπλα στον πηγαίο κώδικα: Component.test.tsx
δίπλα στο Component.tsx. Το happy-dom είναι το περιβάλλον·
React 19 + RTL.
Πρότυπα mock¶
Πάροχοι ΤΝ: mock στο global.fetch και assert στο URL,
headers, body:
beforeEach(() => {
global.fetch = vi.fn(async (input, init) => {
calls.push({url, method, body});
return new Response(JSON.stringify({content: [{type: "text", text: "hi"}]}), {status: 200});
});
});
fake-indexeddb: στην κορυφή κάθε αρχείου Dexie test:
import "fake-indexeddb/auto";
beforeEach(async () => {
await _resetDbForTests();
const {IDBFactory} = await import("fake-indexeddb");
(globalThis as unknown as {indexedDB: IDBFactory}).indexedDB = new IDBFactory();
});
Κάθε τεστ παίρνει φρέσκο in-memory IndexedDB - χωρίς διαρροές.
Mock api/client.ts (παλαιότερες σελίδες):
vi.mock("../api/client", async () => {
const actual = await vi.importActual<typeof import("../api/client")>("../api/client");
return {...actual, api: {...actual.api, users: {...actual.api.users, get: apiGetMock}}};
});
Η σελίδα εισάγει getStorage(), που αναθέτει σε ApiStorage, που
αναθέτει σε api.*. Το mock κόβει στο επίπεδο api.* και
εκτελείται ακόμα μέσω της στοίβας storage.
Playwright E2E¶
cd e2e && npx playwright test
cd e2e && npx playwright test --ui # διαδραστικό
cd e2e && npx playwright test smoke/mobile-viewports.spec.ts
Τα smoke specs καλύπτουν τις κρίσιμες διαδρομές χρήστη:
- Επιλογέας γλώσσας landing + φόρμα onboarding
- Αξιολόγηση 12 ερωτήσεις + render radar
- Εκκίνηση + τέλος + βαθμολόγηση συνεδρίας
- Γλώσσα Ρυθμίσεων + API key
- Δημιουργία Curriculum
- Mobile viewports (iPhone SE, iPhone 14, Pixel 7, iPad)
Τα specs χρησιμοποιούν μόνο selectors data-testid - χωρίς
εύθραυστα CSS selectors. Τα smoke specs ΔΕΝ βρίσκονται στη
διαδρομή make test· ξεκινούν δικούς τους servers σε δικές
τους θύρες και δεν χρειάζονται τρέχουσα εφαρμογή. Ένα τρέχον
make dev μένει ανέγγιχτο.
Κάλυψη¶
Η κάλυψη τρέχει στο CI για κάθε push στο main· κατέβασε τα artifacts:
Στόχοι ανά .claude/rules/quality-checks.md:
- Services + business logic: ελάχ. 95%
- API endpoints: ελάχ. 90%
- Frontend components με λογική: ελάχ. 85%
- Hooks + utilities: ελάχ. 95%
Συνολικά: 85-95% σε επίπεδο έργου.
Pre-commit¶
Hooks: ruff check (auto-fix), ruff format, trailing whitespace, end-of-file fixer, check-yaml, check-merge-conflict. Μόνο backend - το frontend lint τρέχει κατά CI, όχι κατά pre-commit.
CI¶
Το CI χωρίζεται σε δύο βαθμίδες: οι πύλες ορθότητας τρέχουν σε κάθε PR (πρέπει να περάσουν για να γίνει merge), ενώ οι ακριβές ή μόνο-προειδοποιητικές σουίτες τρέχουν στη νυχτερινή βάρδια και κατά την κυκλοφορία (release).
Το .github/workflows/ci.yml τρέχει σε push στο develop / main
και σε κάθε PR (Python 3.12):
- Backend tests (pytest)
- Plugin tests (
make test-plugins, και τα 13 μέσω του backend venv) - Frontend:
tsc --noEmit, ESLint (--max-warnings 0), έλεγχος κυκλικών εξαρτήσεων, Stylelint, Vitest,vite build,npm audit - Pre-commit hooks σε όλα τα αρχεία
- Backend ruff + mypy + pip-audit
- Ελεγκτής απόκλισης τεκμηρίωσης (
verify_docs.py+ συγχρονισμός mkdocs-nav)
Test Impact Analysis (#615): σε ένα PR τρέχουν μόνο τα
επηρεαζόμενα τεστ - vitest run --changed origin/<base> και
pytest --testmon. Τα push στο develop / main, οι νυχτερινές
εκτελέσεις και οι εκτελέσεις release τρέχουν πάντα την ΠΛΗΡΗ σουίτα.
Η μετάπτωση στην πλήρη σουίτα είναι αυτόματη (μη επιλύσιμο base ref ή
αστοχία της cache του testmon).
Περισσότερες πύλες PR ζουν σε δικά τους workflows:
complexity-check.yml- η πύλη ratchet πολυπλοκότητας (make check-complexity-gate, radon για Python + ESLint complexity για TS). Είναι ratchet πάνω σε baseline: αποτυγχάνει μόνο σε ΝΕΟΥΣ ή επιδεινωμένους παραβάτες σε σχέση με το.complexity-baseline, οπότε μπλοκάρει τη νέα πολυπλοκότητα χωρίς να επιβάλλει σάρωση του προϋπάρχοντος χρέους. Η πλήρης, μόνο-προειδοποιητική αναφορά πολυπλοκότητας τρέχει νυχτερινά.cohesion-check.yml- ο έλεγχος μεγέθους αρχείων (πύλη έναντι του.filesize-whitelist) συν δύο πύλες ονομάτων κλάσεων: νεκρά ονόματα κλάσεων CSS (check-dead-classnames.pyέναντι του.dead-classnames-baseline) και η πύλη unstyled-className (--unstyled, ratchet έναντι του.unstyled-classnames-baseline) - έναclassNameτου οποίου όλα τα tokens είναι νεκρά μπλοκάρει το PR. Ο συνοδευτικός έλεγχος μεγέθους φακέλων τρέχει τοπικά μέσωmake check-folder-size.visual-baseline-gate.yml- ένα PR που αλλάζει οπτικά κρίσιμες διαδρομές (Lesson-components, exercise renderers, αρχεία theme/CSS) πρέπει να φέρνει τα επηρεαζόμενα baseline screenshots στο ίδιο PR· escape labelvisual-baselines-unaffectedγια αποδεδειγμένα αδρανείς αλλαγές.testid-reference-gate.yml- αν ένα PR αφαιρεί ή μετονομάζει έναdata-testidπου ένα E2E spec αναφέρει στατικά (σε μια έντονα ορατή για τον χρήστη επιφάνεια) χωρίς να αγγίξει το spec, η πύλη αποτυγχάνει (make check-testid-refs)· escape labeltestid-refs-unaffected.docker-build-smoke.yml- build-only smoke των production compose images (η διαδρομή launcher / install.sh), φιλτραρισμένο κατά διαδρομές σε PRs, επιπλέον σεrelease/**, εβδομαδιαία και κατόπιν dispatch· τοπικάmake docker-build-smoke.
Νυχτερινή βάρδια / Release (όχι σε PRs):
dexie-smoke.yml- η πύλη E2E της λειτουργίας Dexie (καθημερινά + σεrelease/**+ dispatch· τοπικάmake test-dexie-smoke)coverage.yml- αναφορά κάλυψης (καθημερινά + dispatch)security-scan.yml- pip-audit / npm audit / bandit (εβδομαδιαία + σεrelease/**+ dispatch· μόνο προειδοποιητικό)content-stats.yml- απόκλιση στατιστικών περιεχομένου έναντι ενός φρέσκου checkout του αποθετηρίου περιεχομένου (καθημερινά + dispatch)mutation-frontend.yml- Stryker mutation testing (νυχτερινά πίσω από τη μεταβλητή repoENABLE_NIGHTLY_MUTATION+ dispatch· κάθε εκτέλεση μεταλλάσσει μία φέτα των αρχείων, ώστε ο γύρος να χωρά στο χρονικό όριο του job)· το mutation testing του backend χρησιμοποιεί mutmutwebkit-gate.yml- η πύλη layout με πραγματική μηχανή WebKit (κλάσεις σφαλμάτων iOS/Safari που οι πύλες Chromium δομικά δεν μπορούν να δουν), καθημερινά πίσω από τη μεταβλητή repoENABLE_NIGHTLY_WEBKIT, πάντα σεrelease/**και κατόπιν dispatchvisual-regression.yml- η μήτρα των οπτικών baselines (καθημερινά + dispatch· μεupdate_baselines=trueτα baselines ξανααποδίδονται στο CI και ανεβαίνουν ως artifact)visual-baseline-sync.yml- service workflow: αποδίδει τα baselines στο CI και τα κάνει push ως commit στο branch του PR (labelrefresh-visual-baselines, ή dispatch με αριθμό PR) - η εξέταση των εικόνων πριν από το merge παραμένει υποχρεωτική
Το .github/workflows/release-gate.yml τρέχει σε tag pushes:
επαληθεύει ότι τα version pins είναι συγχρονισμένα σε όλα τα αρχεία
που φέρουν έκδοση (χωρίς απόκλιση), ότι τα lockfiles των plugins
ταιριάζουν και ότι τα αναγεννώμενα artifacts είναι ενημερωμένα.