skills/writing-integration-tests/SKILL.md
Guides writing and debugging integration tests for the SCT framework that interact with real external services. Use when creating tests requiring Docker, AWS, GCE, Azure, OCI, or Kubernetes backends. Covers service labeling, credential skip patterns, Docker Scylla fixtures, resource cleanup, and common pitfalls.
npx skillsauth add scylladb/scylla-cluster-tests writing-integration-testsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Write integration tests that clearly label their external service dependencies and clean up after themselves.
All new integration tests go in unit_tests/integration/ (not the root unit_tests/ directory).
All integration tests MUST have @pytest.mark.integration.
SCT separates test execution: sct.py unit-tests runs with -m "not integration", and sct.py integration-tests runs with -m "integration". A test without the marker runs as a unit test, where external services are unavailable and the test will fail.
Every integration test must declare which external services it requires.
Use one or more of these approaches:
pytest.mark.skipif with credential checks and clear reason messagesThis enables developers to know which tests they can run locally and which require cloud credentials.
| Label | Services | Credential Check |
|-------|----------|-----------------|
| docker | Scylla container, Vector Store, Kafka | Docker daemon running |
| aws | EC2, S3, IAM, KMS, SSM, CloudFormation | AWS_ACCESS_KEY_ID or AWS_PROFILE |
| gce | Compute Engine, GCS, KMS | GOOGLE_APPLICATION_CREDENTIALS |
| azure | VMs, Storage, KMS, Resource Groups | AZURE_SUBSCRIPTION_ID |
| oci | Compute, Identity, Network | OCI_CONFIG_FILE |
| kubernetes | EKS, GKE, local Kind cluster | KUBECONFIG or sct.py integration-tests setup |
Every resource created during a test must be destroyed, even if the test fails.
Use yield fixtures with teardown blocks, context managers, or try/finally. Leaked cloud resources cost real money and break subsequent test runs.
writing-unit-tests skillmonkeypatch*_test.py*_test.py at repo root) with a test-cases/ config insteadunit_tests/integration/conftest.py)| Fixture | Scope | Purpose |
|---------|-------|---------|
| docker_scylla | function | Single Scylla container with CQL + Alternator ports |
| docker_scylla_2 | function | Second Scylla node seeded from docker_scylla |
| docker_vector_store | function | Vector Store container connected to docker_scylla |
Configuration via marker:
@pytest.mark.integration
@pytest.mark.docker_scylla_args(
ssl=True,
scylla_docker_image="scylladb/scylla:2025.2.0",
docker_network="my-network",
)
def test_with_ssl(docker_scylla, params):
...
@pytest.mark.integration
@pytest.mark.sct_config(files="test-cases/my-test.yaml")
def test_with_config(docker_scylla, params):
# params is loaded from the specified config file
...
| Fixture | Scope | Purpose |
|---------|-------|---------|
| events | module | Shared event system (efficient for many tests) |
| events_function_scope | function | Per-test event system (better isolation) |
@pytest.mark.integration
def test_cql_query(docker_scylla):
"""Test CQL connectivity to Scylla container.
External services: Docker (Scylla container)
"""
session = docker_scylla.cql_connection()
result = session.execute("SELECT cluster_name FROM system.local")
assert result.one()
For tests using moto (fully mocked AWS — no real credentials needed), write them as unit tests without the integration marker:
import boto3
from moto import mock_aws
@mock_aws
def test_s3_operations():
"""Test S3 storage operations with mocked AWS (no real credentials needed)."""
s3 = boto3.client("s3", region_name="us-east-1")
s3.create_bucket(Bucket="test-bucket")
Note:
@mock_awsintercepts all boto3 calls — no real AWS access occurs. These tests can run as unit tests. Only use@pytest.mark.integrationfor tests that need real AWS credentials.
For tests that need real AWS:
import os
import pytest
pytestmark = [
pytest.mark.integration,
pytest.mark.skipif(
not os.environ.get("AWS_ACCESS_KEY_ID"),
reason="AWS credentials not configured — set AWS_ACCESS_KEY_ID",
),
]
import os
import pytest
pytestmark = [
pytest.mark.integration,
pytest.mark.skipif(
not os.environ.get("OCI_CONFIG_FILE"),
reason="OCI credentials not configured — set OCI_CONFIG_FILE",
),
]
def test_oci_instances():
"""Test OCI compute instance listing.
External services: OCI Compute, OCI Identity
"""
...
Kubernetes tests require the sct.py integration-tests runner which sets up a local Kind cluster:
@pytest.mark.integration
def test_k8s_operator(k8s_cluster):
"""Test K8s operator deployment.
External services: Kubernetes (local Kind cluster)
Prerequisites: Run via `sct.py integration-tests` (sets up LocalKindCluster)
"""
...
docker ps — is Docker running?docker pull scylladb/scylla-nightly:latestdocker ps — is another Scylla container using the same ports?docker logs <container_id>wait.wait_for calls.docker exec <id> nodetool status-s flag to see stdout during waits:
uv run python -m pytest unit_tests/test_module.py -v -s -m integration -n0
skipif guards.docker --version.moto for AWS tests when possible.docker ps -a | grep scylladocker rm -f <container_id>yield teardown or finally block.# Run all integration tests (sets up K8s prerequisites)
uv run sct.py integration-tests
# Run a specific integration test file
uv run sct.py integration-tests -t integration/test_your_module.py
# Run a specific test with verbose output (no parallel)
uv run python -m pytest unit_tests/integration/test_your_module.py::test_function -v -s -m integration -n0
# Run integration tests with more parallel workers
uv run sct.py integration-tests -n 8
After running, validate:
docker ps — confirm the Scylla container is running during the testdocker ps -a | grep scylla should show no leftover containers| File | Content | |------|---------| | common-pitfalls.md | Integration-specific pitfalls and anti-patterns with before/after fixes |
| Workflow | Purpose | |----------|---------| | write-an-integration-test.md | 4-phase process for writing a new integration test |
A well-written SCT integration test:
@pytest.mark.integration on every test function or at module leveldocker_scylla fixture for Scylla container tests (not custom Docker setup)xdist_group when tests share expensive resourcesuv run sct.py pre-commit checksdevelopment
Guides writing and debugging unit tests for the SCT framework using pytest conventions. Use when creating new test files in unit_tests/, adding test cases, mocking external services, setting up fixtures, or reviewing test coverage. Covers network-blocking patterns, FakeRemoter, moto for AWS mocking, monkeypatch, and common pitfalls.
development
Use when asked to generate an implementation plan, draft a plan, save a plan, or design a feature rollout for the SCT repository. Supports two formats: full 7-section plans for multi-phase work (1K+ LOC, tracked in MASTER.md) and lightweight mini-plans for single-PR changes (under 1K LOC, stored in docs/plans/mini-plans/). Routes automatically based on PR plans label, user input, or task size estimate.
development
Guides writing new nemesis (chaos engineering disruptions) for the SCT framework. Use when creating a new NemesisBaseClass subclass, adding disruption logic, setting nemesis flags, or configuring target node pools. Covers the sdcm/nemesis/ package structure, auto-discovery, flag filtering, CI configuration, and unit testing patterns.
development
Profile Python code in SCT to find CPU, memory, and concurrency bottlenecks using cProfile, scalene, memray, and py-spy. Use when a test or framework operation is unexpectedly slow, memory usage grows unbounded, you need to find which functions dominate CPU time, or you want to verify that an optimization actually improved performance. Covers profiling unit tests and full SCT test runs.