refactor: move slow performance tests to extra test suite
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Simon 2026-01-28 12:00:32 +01:00
parent fce01ddf83
commit 67e06e12ce
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
10 changed files with 433 additions and 5 deletions

View file

@ -83,8 +83,8 @@ steps:
- mix local.hex --force - mix local.hex --force
# Fetch dependencies # Fetch dependencies
- mix deps.get - mix deps.get
# Run tests # Run fast tests (excludes slow/performance tests)
- mix test - mix test --exclude slow
- name: rebuild-cache - name: rebuild-cache
image: drillster/drone-volume-cache image: drillster/drone-volume-cache
@ -178,3 +178,52 @@ steps:
- unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL - unset GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
- renovate-config-validator - renovate-config-validator
- renovate - renovate
---
kind: pipeline
type: docker
name: nightly-tests
trigger:
event:
- cron
cron:
- "0 2 * * *" # Run at 2 AM daily
services:
- name: postgres
image: docker.io/library/postgres:18.1
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
steps:
- name: wait_for_postgres
image: docker.io/library/postgres:18.1
commands:
# Wait for postgres to become available
- |
for i in {1..20}; do
if pg_isready -h postgres -U postgres; then
exit 0
else
true
fi
sleep 2
done
echo "Postgres did not become available, aborting."
exit 1
- name: test-slow
image: docker.io/library/elixir:1.18.3-otp-27
environment:
MIX_ENV: test
TEST_POSTGRES_HOST: postgres
TEST_POSTGRES_PORT: 5432
commands:
# Install hex package manager
- mix local.hex --force
# Fetch dependencies
- mix deps.get
# Run only all tests
- mix test

View file

@ -1654,6 +1654,28 @@ end
- Mock external services - Mock external services
- Use fixtures efficiently - Use fixtures efficiently
**Performance Tests:**
Performance tests that explicitly validate performance characteristics should be tagged with `@tag :slow` or `@moduletag :slow` to exclude them from standard test runs. This improves developer feedback loops while maintaining comprehensive coverage.
**When to Tag as `:slow`:**
- Tests that explicitly measure execution time or validate performance characteristics
- Tests that use large datasets (e.g., 50+ records) to test scalability
- Tests that validate query optimization (N+1 prevention, index usage)
**Running Tests:**
```bash
# Fast tests only (default)
mix test --exclude slow
# Performance tests only
mix test --only slow
# All tests
mix test
```
--- ---
## 5. Security Guidelines ## 5. Security Guidelines

View file

@ -22,7 +22,7 @@ seed-database:
start-database: start-database:
docker compose up -d docker compose up -d
ci-dev: lint audit test ci-dev: lint audit test-fast
gettext: gettext:
mix gettext.extract mix gettext.extract
@ -44,6 +44,18 @@ audit:
test *args: install-dependencies test *args: install-dependencies
mix test {{args}} mix test {{args}}
# Run only fast tests (excludes slow/performance tests)
test-fast *args: install-dependencies
mix test --exclude slow {{args}}
# Run only slow/performance tests
test-slow *args: install-dependencies
mix test --only slow {{args}}
# Run all tests (fast + slow)
test-all *args: install-dependencies
mix test {{args}}
format: format:
mix format mix format

View file

@ -116,8 +116,67 @@ Based on the initial analysis (`mix test --slowest 20`), the following test file
1. ✅ **Done:** Optimize seeds tests (this document) 1. ✅ **Done:** Optimize seeds tests (this document)
2. ⏳ **Next:** Review and optimize LiveView tests with large data setups 2. ⏳ **Next:** Review and optimize LiveView tests with large data setups
3. ⏳ **Next:** Implement shared fixtures for policy tests 3. ⏳ **Next:** Implement shared fixtures for policy tests
4. ⏳ **Next:** Tag performance tests as `:slow` for conditional execution 4. ✅ **Done:** Tag performance tests as `:slow` for conditional execution
5. ⏳ **Future:** Consider nightly integration test suite for comprehensive coverage 5. ✅ **Done:** Nightly integration test suite for comprehensive coverage
---
## Slow Test Suite
Performance tests have been tagged with `@tag :slow` and separated into a dedicated test suite.
### Structure
- **Performance Tests:** Explicit tests that validate performance characteristics (e.g., N+1 query prevention, filter performance with large datasets)
- **Tagging:** All performance tests are tagged with `@tag :slow` or `@moduletag :slow`
- **Execution:** Standard test runs exclude slow tests, but they can be executed on demand
### Execution
**Fast Tests (Default):**
```bash
just test-fast
# or
mix test --exclude slow
# With specific files or options
just test-fast test/membership/member_test.exs
just test-fast --seed 123
```
**Performance Tests Only:**
```bash
just test-slow
# or
mix test --only slow
# With specific files or options
just test-slow test/mv_web/member_live/index_test.exs
just test-slow --seed 123
```
**All Tests:**
```bash
just test
# or
mix test
# With specific files or options
just test-all test/mv_web/
just test-all --max-failures 5
```
**Note:** All suite commands (`test-fast`, `test-slow`, `test-all`) support additional arguments. The suite semantics (tags) are always preserved - additional arguments are appended to the command.
### CI/CD Integration
- **Standard CI:** Runs `mix test --exclude slow` for faster feedback loops
- **Nightly Builds:** Separate pipeline (`nightly-tests`) runs daily at 2 AM and executes all performance tests
- **Manual Execution:** Performance tests can be executed anytime with `just test-slow`
### Further Details
See [test-slow-suite.md](test-slow-suite.md) for complete documentation of the Slow Test Suite.
--- ---
@ -148,6 +207,7 @@ Based on the initial analysis (`mix test --slowest 20`), the following test file
## References ## References
- **Detailed Documentation:** `docs/test-optimization-seeds.md` - **Detailed Documentation:** `docs/test-optimization-seeds.md`
- **Slow Test Suite:** `docs/test-slow-suite.md`
- **Test File:** `test/seeds_test.exs` - **Test File:** `test/seeds_test.exs`
- **Original Analysis:** Internal benchmarking session (2026-01-28) - **Original Analysis:** Internal benchmarking session (2026-01-28)
- **Related Guidelines:** `CODE_GUIDELINES.md` - Section 4 (Testing Standards) - **Related Guidelines:** `CODE_GUIDELINES.md` - Section 4 (Testing Standards)

280
docs/test-slow-suite.md Normal file
View file

@ -0,0 +1,280 @@
# Slow Test Suite Documentation
**Date:** 2026-01-28
**Status:** ✅ Active
---
## Overview
The Slow Test Suite contains performance tests that explicitly validate performance characteristics of the application. These tests are intentionally slower because they use larger datasets or test performance-critical paths (e.g., N+1 query prevention, filter performance with many records).
These tests are marked with `@tag :slow` or `@moduletag :slow` and are excluded from standard test runs to improve developer feedback loops while maintaining comprehensive coverage.
---
## Purpose
Performance tests serve to:
1. **Validate Performance Characteristics:** Ensure queries and operations perform within acceptable time limits
2. **Prevent Regressions:** Catch performance regressions before they reach production
3. **Test Scalability:** Verify that the application handles larger datasets efficiently
4. **N+1 Query Prevention:** Ensure proper preloading and query optimization
---
## Identified Performance Tests
### 1. Member LiveView - Boolean Filter Performance
**File:** `test/mv_web/member_live/index_test.exs`
**Test:** `"boolean filter performance with 150 members"`
**Duration:** ~3.8 seconds
**Purpose:** Validates that boolean custom field filtering performs efficiently with 150 members
**What it tests:**
- Creates 150 members (75 with `true`, 75 with `false` for a boolean custom field)
- Tests filter performance (< 1 second requirement)
- Verifies correct filtering behavior
### 2. Group LiveView - Index Performance
**File:** `test/mv_web/live/group_live/index_test.exs`
**Describe Block:** `"performance"`
**Tests:** 2 tests
**Purpose:** Validates efficient page loading and member count calculation
**What it tests:**
- Page loads efficiently with many groups (no N+1 queries)
- Member count calculation is efficient
### 3. Group LiveView - Show Performance
**File:** `test/mv_web/live/group_live/show_test.exs`
**Describe Block:** `"performance"`
**Tests:** 2 tests
**Purpose:** Validates efficient member list loading and slug lookup
**What it tests:**
- Member list is loaded efficiently (no N+1 queries)
- Slug lookup uses unique_slug index efficiently
### 4. Member LiveView - Membership Fee Status Performance
**File:** `test/mv_web/member_live/index_membership_fee_status_test.exs`
**Describe Block:** `"performance"`
**Tests:** 1 test
**Purpose:** Validates efficient cycle loading without N+1 queries
**What it tests:**
- Cycles are loaded efficiently without N+1 queries
- Multiple members with cycles render without performance issues
### 5. Group Integration - Query Performance
**File:** `test/membership/group_integration_test.exs`
**Describe Block:** `"Query Performance"`
**Tests:** 1 test
**Purpose:** Validates N+1 query prevention for group-member relationships
**What it tests:**
- Preloading groups with members avoids N+1 queries
- Query optimization for many-to-many relationships
---
## Running Slow Tests
### Local Development
**Run only fast tests (default):**
```bash
just test-fast
# or
mix test --exclude slow
# With specific files or options
just test-fast test/membership/member_test.exs
just test-fast --seed 123
```
**Run only performance tests:**
```bash
just test-slow
# or
mix test --only slow
# With specific files or options
just test-slow test/mv_web/member_live/index_test.exs
just test-slow --seed 123
```
**Run all tests (fast + slow):**
```bash
just test
# or
mix test
# With specific files or options
just test-all test/mv_web/
just test-all --max-failures 5
```
**Note:** All suite commands (`test-fast`, `test-slow`, `test-all`) support additional arguments. The suite semantics (tags) are always preserved - additional arguments are appended to the command.
### CI/CD
**Standard CI Pipeline:**
- Runs `mix test --exclude slow` for faster feedback
- Executes on every push to any branch
**Nightly Pipeline:**
- Runs `mix test --only slow` for comprehensive performance coverage
- Executes daily at 2 AM via cron trigger
- Pipeline name: `nightly-tests`
---
## Best Practices for New Performance Tests
### When to Tag as `:slow`
Tag tests as `:slow` when they:
1. **Explicitly test performance:** Tests that measure execution time or validate performance characteristics
2. **Use large datasets:** Tests that create many records (e.g., 50+ members, 100+ records)
3. **Test scalability:** Tests that verify the application handles larger workloads
4. **Validate query optimization:** Tests that ensure N+1 queries are prevented
### When NOT to Tag as `:slow`
Do NOT tag tests as `:slow` if they are:
1. **Simply slow by accident:** Tests that are slow due to inefficient setup, not intentional performance testing
2. **Slow due to bugs:** Tests that are slow because of actual performance bugs (fix the bug instead)
3. **Integration tests:** Integration tests should be tagged separately if needed (`@tag :integration`)
### Tagging Guidelines
**For single tests:**
```elixir
@tag :slow
test "boolean filter performance with 150 members" do
# test implementation
end
```
**For describe blocks (all tests in block):**
```elixir
@moduletag :slow
describe "performance" do
test "page loads efficiently" do
# test implementation
end
test "member count is efficient" do
# test implementation
end
end
```
---
## Performance Test Structure
### Recommended Structure
```elixir
defmodule MvWeb.SomeLiveViewTest do
use MvWeb.ConnCase, async: true
# Regular tests here (not tagged)
@moduletag :slow
describe "performance" do
test "loads efficiently without N+1 queries" do
# Create test data
# Measure/validate performance
# Assert correct behavior
end
test "handles large datasets efficiently" do
# Create large dataset
# Measure performance
# Assert performance requirements
end
end
end
```
### Performance Assertions
Performance tests should include explicit performance assertions:
```elixir
# Example: Time-based assertion
start_time = System.monotonic_time(:millisecond)
# ... perform operation ...
end_time = System.monotonic_time(:millisecond)
duration = end_time - start_time
assert duration < 1000, "Operation took #{duration}ms, expected < 1000ms"
```
```elixir
# Example: Query count assertion (using Ecto query logging)
# Verify no N+1 queries by checking query count
```
---
## Monitoring and Maintenance
### Regular Review
- **Quarterly Review:** Review slow tests quarterly to ensure they're still relevant
- **Performance Baselines:** Update performance assertions if legitimate performance improvements occur
- **Test Cleanup:** Remove or optimize tests that become redundant
### Success Metrics
- ✅ Performance tests catch regressions before production
- ✅ Standard test runs complete in < 3 minutes
- ✅ Nightly builds complete successfully
- ✅ No false positives in performance tests
### Troubleshooting
**If a performance test fails:**
1. **Check if it's a real regression:** Compare with previous runs
2. **Check CI environment:** Ensure CI has adequate resources
3. **Review test data:** Ensure test data setup is correct
4. **Check for flakiness:** Run test multiple times to verify consistency
**If a performance test is too slow:**
1. **Review test implementation:** Look for inefficiencies in test setup
2. **Consider reducing dataset size:** If still representative
3. **Split into smaller tests:** If testing multiple concerns
---
## Related Documentation
- **Test Optimization Summary:** `docs/test-optimization-summary.md`
- **Seeds Test Optimization:** `docs/test-optimization-seeds.md`
- **Testing Standards:** `CODE_GUIDELINES.md` - Section 4 (Testing Standards)
- **CI/CD Configuration:** `.drone.yml`
---
## Changelog
### 2026-01-28: Initial Setup
- Marked 5 performance test suites with `@tag :slow` or `@moduletag :slow`
- Added `test-fast`, `test-slow`, and `test-all` commands to Justfile
- Updated CI to exclude slow tests from standard runs
- Added nightly CI pipeline for slow tests
- Created this documentation

View file

@ -79,6 +79,7 @@ defmodule Mv.Membership.GroupIntegrationTest do
end end
end end
@moduletag :slow
describe "Query Performance" do describe "Query Performance" do
test "preloading groups with members avoids N+1 queries", %{actor: actor} do test "preloading groups with members avoids N+1 queries", %{actor: actor} do
# Create test data # Create test data

View file

@ -114,6 +114,7 @@ defmodule MvWeb.GroupLive.IndexTest do
end end
end end
@moduletag :slow
describe "performance" do describe "performance" do
test "page loads efficiently with many groups", %{conn: conn} do test "page loads efficiently with many groups", %{conn: conn} do
# Create multiple groups # Create multiple groups

View file

@ -219,6 +219,7 @@ defmodule MvWeb.GroupLive.ShowTest do
end end
end end
@moduletag :slow
describe "performance" do describe "performance" do
test "member list is loaded efficiently (no N+1 queries)", %{conn: conn} do test "member list is loaded efficiently (no N+1 queries)", %{conn: conn} do
group = Fixtures.group_fixture() group = Fixtures.group_fixture()

View file

@ -238,6 +238,7 @@ defmodule MvWeb.MemberLive.IndexMembershipFeeStatusTest do
end end
end end
@moduletag :slow
describe "performance" do describe "performance" do
test "loads cycles efficiently without N+1 queries", %{conn: conn} do test "loads cycles efficiently without N+1 queries", %{conn: conn} do
fee_type = create_fee_type(%{interval: :yearly}) fee_type = create_fee_type(%{interval: :yearly})

View file

@ -1775,6 +1775,7 @@ defmodule MvWeb.MemberLive.IndexTest do
assert Enum.any?(boolean_fields_after, &(&1.name == "Newly Added Field")) assert Enum.any?(boolean_fields_after, &(&1.name == "Newly Added Field"))
end end
@tag :slow
test "boolean filter performance with 150 members", %{conn: conn} do test "boolean filter performance with 150 members", %{conn: conn} do
system_actor = Mv.Helpers.SystemActor.get_system_actor() system_actor = Mv.Helpers.SystemActor.get_system_actor()
conn = conn_with_oidc_user(conn) conn = conn_with_oidc_user(conn)