# Test Performance Optimization

**Last Updated:** 2026-01-28  
**Status:** Implemented

This document records the test-suite performance work and — most importantly — the conventions that govern how tests are tagged and run. The seeds-test rationale in §1 is the canonical reference linked from `test/seeds_test.exs`.

Baseline result: the standard (fast) suite runs ~368 s (~6.1 min) vs. ~445 s before; the full suite (all tests) ~7.4 min. Slow-tagged tests (~25, >1 s each, ~77 s total) are excluded from standard runs and executed via promotion before merge.

---

## 1. Seeds Test Suite — coverage mapping

The seeds tests were reduced from 13 to 4. The 9 removed tests were dropped because their assertions are already covered by domain-specific test suites — this mapping is the justification and must be preserved:

| Removed seeds test | Covered by |
|--------------------|-----------|
| `"at least one member has no membership fee type assigned"` | `membership_fees/*_test.exs` |
| `"each membership fee type has at least one member"` | `membership_fees/*_test.exs` |
| `"members with fee types have cycles with various statuses"` | `cycle_generator_test.exs` |
| `"creates all 5 authorization roles with correct permission sets"` | `authorization/*_test.exs` |
| `"all roles have valid permission_set_names"` | `authorization/permission_sets_test.exs` |
| `"does not change role of users who already have a role"` | merged into general idempotency test |
| `"role creation is idempotent"` (detailed) | merged into general idempotency test |

### 4 retained critical-bootstrap tests (and why)

These guard deployment-critical invariants that nothing else covers and must stay in the **fast** suite:

1. **Smoke test** — seeds run successfully and create basic data.
2. **Idempotency** — seeds can be re-run without duplicating data.
3. **Admin bootstrap** — admin user exists with Admin role (critical for initial access).
4. **System-role bootstrap** — `Mitglied` system role exists (critical for user registration).

If a new critical bootstrap requirement appears, add a test to the "Critical bootstrap invariants" section in `test/seeds_test.exs`. Removed-test risk is low: a smoke-test failure surfaces broken seeds, and domain tests verify business logic independently of seeds content.

---

## 2. Tagging convention: `:slow`

Tests are split into **fast** (standard CI) and **slow** (run via promotion before merge). A test is tagged `@tag :slow` when **all** of:

- execution time > 1 s, **and**
- low risk — does not catch critical regressions in core business logic, **and**
- it is a UI/display/formatting test, a workflow-detail test, or an edge case with a large dataset (performance tests with 50+ records always qualify).

**Never** tag as `:slow`:

- Core CRUD (Member/User create/update/destroy)
- Basic authentication/authorization
- Critical bootstrap (admin user, system roles)
- Email synchronization
- Representative policy tests (one per permission set + action)
- A test that is merely slow due to inefficient setup or a bug — fix the setup/bug instead
- An integration test — use `@tag :integration` instead

Use **`@describetag :slow`** (not `@moduletag`) for describe blocks, so unrelated tests in the same module are not tagged.

One-off isolation fix worth noting as a pattern: a test that loaded *all* members was slow in full runs because of cross-test data accumulation; constraining it with a search query (`/members?query=Alice`) made it both faster and properly isolated. Prefer query filters over loading all records.

---

## 3. Execution model

| Mode | Command | Contents | Time |
|------|---------|----------|------|
| Fast (default) | `just test-fast` / `mix test --exclude slow --exclude ui` | everything except `:slow` and `:ui` | ~6 min |
| Slow only | `just test-slow` / `mix test --only slow` | the ~25 `:slow` tests | ~1.3 min |
| Full | `just test` / `mix test` | all tests | ~7.4 min |

CI: standard pipeline (`check-fast`) runs `mix test --exclude slow --exclude ui`. The full suite (`check-full`) is triggered by promoting a Drone build to `production` and is required before merging to `main` (branch protection).

To find the slowest tests, run `mix test --slowest N` ad hoc. `test/test_helper.exs` also carries a `slowest: 10` option for `ExUnit.start/1`, but it is commented out by default — uncomment it to print the 10 slowest tests at the end of every run.

---

## 4. Test organization

Tests mirror the `lib/` structure:

```
test/
├── accounts/              # Accounts domain
├── membership/            # Membership domain
├── membership_fees/       # Membership fees domain
├── mv/                    # Core (accounts, membership, authorization)
├── mv_web/                # Web layer (controllers, live, components)
└── support/               # conn_case.ex, data_case.ex
```

---

## References

- Testing Standards: `CODE_GUIDELINES.md` §4
- CI/CD: `.drone.jsonnet`
- Test helper: `test/test_helper.exs`
- Just commands: `Justfile` (`test-fast`, `test-slow`, `test`)
