Test Runner Patterns¶
Three patterns for organizing V&V test suites, adapted from real-world integration experience.
Pattern 1: Static Analysis Tests¶
Tests that validate the spec itself — no running system required.
import pytest
from vnvspec import Spec
@pytest.mark.vnvspec("REQ-SPEC-001")
def test_all_requirements_have_rationale(spec: Spec):
for req in spec.requirements:
assert req.rationale, f"{req.id} has no rationale"
@pytest.mark.vnvspec("REQ-SPEC-002")
def test_requirement_quality(spec: Spec):
for req in spec.requirements:
violations = req.check_quality(profile="web-app")
errors = [v for v in violations if v.severity == "error"]
assert not errors, f"{req.id}: {errors}"
Pattern 2: Live API Tests¶
Tests that hit a running service to verify functional requirements.
import httpx
import pytest
@pytest.mark.vnvspec("REQ-API-001")
def test_health_endpoint(api_url: str):
r = httpx.get(f"{api_url}/health")
assert r.status_code == 200
@pytest.mark.vnvspec("REQ-API-002")
def test_auth_required(api_url: str):
r = httpx.get(f"{api_url}/protected")
assert r.status_code == 401
Pattern 3: Build Verification Tests¶
Tests that verify build artifacts after CI produces them.
import pytest
from pathlib import Path
@pytest.mark.vnvspec("REQ-BUILD-001")
def test_docker_image_exists():
result = subprocess.run(["docker", "images", "-q", "myapp:latest"], capture_output=True)
assert result.stdout.strip(), "Docker image not built"
@pytest.mark.vnvspec("REQ-BUILD-002")
def test_migration_scripts_present():
migrations = list(Path("migrations").glob("*.sql"))
assert len(migrations) > 0
Combining patterns¶
Run each pattern in a separate pytest session or with markers:
# Static checks (no infrastructure needed)
pytest -m "static" --vnvspec-spec=spec.yaml --vnvspec-report=static.json
# Live API tests (requires running service)
pytest -m "live" --vnvspec-spec=spec.yaml --vnvspec-report=live.json
# Build verification (requires build artifacts)
pytest -m "build" --vnvspec-spec=spec.yaml --vnvspec-report=build.json