
Test-Driven Development
FreeImplement a mandatory TDD workflow for Prowler components.
Free · Opens the source repo
What Test-Driven Development does
The Test-Driven Development (TDD) skill provides a structured workflow specifically designed for all components of the Prowler framework, including the UI, SDK, and API. This skill mandates a TDD approach, ensuring that developers write tests before any production code is implemented. By following the TDD cycle of RED, GREEN, and REFACTOR, users can systematically build features while maintaining high code quality and reliability.
The workflow begins with an assessment phase where developers identify the component they are working on—be it UI, SDK, or API. Each component has its own testing framework and conventions, such as Vitest for UI and pytest for SDK and API. The skill guides users through finding existing tests, checking coverage, and determining whether new tests are needed. This structured approach minimizes the risk of introducing bugs and ensures that all new functionality is thoroughly tested from the outset.
In the RED phase, developers write failing tests that define the expected behavior of new features or reproduce existing bugs. This is followed by the GREEN phase, where the minimum code necessary to pass the tests is implemented. Finally, the REFACTOR phase allows developers to clean up the code while ensuring that all tests continue to pass. The skill emphasizes the importance of triangulation, where multiple tests are created to cover various scenarios, ensuring robust and error-free code.
This skill is ideal for developers and teams using the Prowler framework who want to enforce a disciplined approach to software development. It is particularly beneficial for those looking to improve code quality and maintainability through consistent testing practices.
When to use it
Use this skill whenever implementing new features, fixing bugs, or refactoring code within the Prowler framework.
When not to use it
This skill may not be suitable for projects that do not require a TDD approach or for components outside the Prowler framework.
What you can build with it
Implementing New Features
Use the TDD skill to ensure that new features are developed with tests written first, reducing the likelihood of bugs.
Fixing Bugs
When addressing bugs, the TDD skill guides you to write tests that reproduce the issue before making any code changes.
Refactoring Code
Leverage the TDD skill to safely refactor existing code by ensuring all tests pass before and after the changes.
How to install Test-Driven Development
View source1. Install with the skills CLI
npx skills add prowler-cloud/prowler/tdd --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by prowler-cloudTDD Cycle (MANDATORY)
+-----------------------------------------+
| RED -> GREEN -> REFACTOR |
| ^ | |
| +------------------------+ |
+-----------------------------------------+
The question is NOT "should I write tests?" but "what tests do I need?"
The Three Laws of TDD
- No production code until you have a failing test
- No more test than necessary to fail
- No more code than necessary to pass
Detect Your Stack
Before starting, identify which component you're working on:
| Working in | Stack | Runner | Test pattern | Details |
|---|---|---|---|---|
ui/ | TypeScript / React | Vitest + RTL | *.test.{ts,tsx} (co-located) | See vitest skill |
prowler/ | Python | pytest + moto | *_test.py (suffix) in tests/ | See prowler-test-sdk skill |
api/ | Python / Django | pytest + django | test_*.py (prefix) in api/src/backend/**/tests/ | See prowler-test-api skill |
Phase 0: Assessment (ALWAYS FIRST)
Before writing ANY code:
UI (ui/)
# 1. Find existing tests
fd "*.test.tsx" ui/components/feature/
# 2. Check coverage
pnpm test:coverage -- components/feature/
# 3. Read existing tests
SDK (prowler/)
# 1. Find existing tests
fd "*_test.py" tests/providers/aws/services/ec2/
# 2. Run specific test
uv run pytest tests/providers/aws/services/ec2/ec2_ami_public/ -v
# 3. Read existing tests
API (api/)
# 1. Find existing tests
fd "test_*.py" api/src/backend/api/tests/
# 2. Run specific test
uv run pytest api/src/backend/api/tests/test_models.py -v
# 3. Read existing tests
Decision Tree (All Stacks)
+------------------------------------------+
| Does test file exist for this code? |
+----------+-----------------------+-------+
| NO | YES
v v
+------------------+ +------------------+
| CREATE test file | | Check coverage |
| -> Phase 1: RED | | for your change |
+------------------+ +--------+---------+
|
+--------+--------+
| Missing cases? |
+---+---------+---+
| YES | NO
v v
+-----------+ +-----------+
| ADD tests | | Proceed |
| Phase 1 | | Phase 2 |
+-----------+ +-----------+
Phase 1: RED - Write Failing Tests
For NEW Functionality
UI (Vitest)
describe("PriceCalculator", () => {
it("should return 0 for quantities below threshold", () => {
// Given
const quantity = 3;
// When
const result = calculateDiscount(quantity);
// Then
expect(result).toBe(0);
});
});
SDK (pytest)
class Test_ec2_ami_public:
@mock_aws
def test_no_public_amis(self):
# Given - No AMIs exist
aws_provider = set_mocked_aws_provider([AWS_REGION_US_EAST_1])
with mock.patch("prowler...ec2_service", new=EC2(aws_provider)):
from prowler...ec2_ami_public import ec2_ami_public
# When
check = ec2_ami_public()
result = check.execute()
# Then
assert len(result) == 0
API (pytest-django)
@pytest.mark.django_db
class TestResourceModel:
def test_create_resource_with_tags(self, aws_provider):
# Given
provider = aws_provider
tenant_id = provider.tenant_id
# When
resource = Resource.objects.create(
tenant_id=tenant_id, provider=provider,
uid="arn:aws:ec2:us-east-1:123456789:instance/i-1234",
name="test", region="us-east-1", service="ec2", type="instance",
)
# Then
assert resource.uid == "arn:aws:ec2:us-east-1:123456789:instance/i-1234"
Run -> MUST fail: Test references code that doesn't exist yet.
For BUG FIXES
Write a test that reproduces the bug first:
UI: expect(() => render(<DatePicker value={null} />)).not.toThrow();
SDK: assert result[0].status == "FAIL" # Currently returns PASS incorrectly
API: assert response.status_code == 403 # Currently returns 200
Run -> Should FAIL (reproducing the bug).
For REFACTORING
Capture ALL current behavior BEFORE refactoring:
# Any stack: run ALL existing tests, they should PASS
# This is your safety net - if any fail after refactoring, you broke something
Run -> All should PASS (baseline).
Phase 2: GREEN - Minimum Code
Write the MINIMUM code to make the test pass. Hardcoding is valid for the first test.
UI:
// Test expects calculateDiscount(100, 10) === 10
function calculateDiscount() {
return 10; // FAKE IT - hardcoded is valid for first test
}
Python (SDK/API):
# Test expects check.execute() returns 0 results
def execute(self):
return [] # FAKE IT - hardcoded is valid for first test
This passes. But we're not done...
Phase 3: Triangulation (CRITICAL)
One test allows faking. Multiple tests FORCE real logic.
Add tests with different inputs that break the hardcoded value:
| Scenario | Required? |
|---|---|
| Happy path | YES |
| Zero/empty values | YES |
| Boundary values | YES |
| Different valid inputs | YES (breaks fake) |
| Error conditions | YES |
UI:
it("should calculate 10% discount", () => {
expect(calculateDiscount(100, 10)).toBe(10);
});
// ADD - breaks the fake:
it("should calculate 15% on 200", () => {
expect(calculateDiscount(200, 15)).toBe(30);
});
it("should return 0 for 0% rate", () => {
expect(calculateDiscount(100, 0)).toBe(0);
});
Python:
def test_single_public_ami(self):
# Different input -> breaks hardcoded empty list
assert len(result) == 1
assert result[0].status == "FAIL"
def test_private_ami(self):
assert result[0].status == "PASS"
Now fake BREAKS -> Real implementation required.
Phase 4: REFACTOR
Tests GREEN -> Improve code quality WITHOUT changing behavior.
- Extract functions/methods
- Improve naming
- Add types/validation
- Reduce duplication
Run tests after EACH change -> Must stay GREEN.
Quick Reference
+------------------------------------------------+
| TDD WORKFLOW |
+------------------------------------------------+
| 0. ASSESS: What tests exist? What's missing? |
| |
| 1. RED: Write ONE failing test |
| +-- Run -> Must fail with clear error |
| |
| 2. GREEN: Write MINIMUM code to pass |
| +-- Fake It is valid for first test |
| |
| 3. TRIANGULATE: Add tests that break the fake |
| +-- Different inputs, edge cases |
| |
| 4. REFACTOR: Improve with confidence |
| +-- Tests stay green throughout |
| |
| 5. REPEAT: Next behavior/requirement |
+------------------------------------------------+
Anti-Patterns (NEVER DO)
# ANY language:
# 1. Code first, tests after
def new_feature(): ... # Then writing tests = USELESS
# 2. Skip triangulation
# Single test allows faking forever
# 3. Test implementation details
assert component.state.is_loading == True # BAD - test behavior, not internals
assert mock_service.call_count == 3 # BAD - brittle coupling
# 4. All tests at once before any code
# Write ONE test, make it pass, THEN write the next
# 5. Giant test methods
# Each test should verify ONE behavior
Commands by Stack
UI (ui/)
pnpm test # Watch mode
pnpm test:run # Single run (CI)
pnpm test:coverage # Coverage report
pnpm test ComponentName # Filter by name
SDK (prowler/)
uv run pytest tests/path/ -v # Run specific tests
uv run pytest tests/path/ -v -k "test_name" # Filter by name
uv run pytest -n auto tests/ # Parallel run
uv run pytest --cov=./prowler tests/ # Coverage
API (api/)
uv run pytest -x --tb=short # Run all (stop on first fail)
uv run pytest api/src/backend/api/tests/test_file.py # Specific file
uv run pytest -k "test_name" -v # Filter by name
Frequently asked questions about Test-Driven Development
Similar skills
Spring Boot Testing
Master testing techniques for Spring Boot 4 applications.
GitHub Issues
Manage GitHub issues efficiently with MCP tools.
Geofeed Tuner
Optimize your IP geolocation feeds in CSV format.
Batch Files
Master Windows batch scripting for automation and task management.
Adobe Illustrator Scripting
Automate your Illustrator workflows with ExtendScript.
Plugin Structure
Create and organize Claude Code plugins effectively.
