Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Pytest testing patterns for Python. Trigger: When writing or refactoring pytest tests (fixtures, mocking, parametrize, markers). For Prowler-specific API/SDK testing conventions, also use prowler-test-api or prowler-test-sdk.
.claude/skills/itamarzand88-pytest/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-17 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 84% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 151% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 126% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 144% | 0% |
<!-- source: prowler-pytest — https://raw.githubusercontent.com/prowler-cloud/prowler/master/skills/pytest/SKILL.md -->
pythonimport pytest class TestUserService: def test_create_user_success(self): user = create_user(name="John", email="john@test.com") assert user.name == "John" assert user.email == "john@test.com" def test_create_user_invalid_email_fails(self): with pytest.raises(ValueError, match="Invalid email"): create_user(name="John", email="invalid")
pythonimport pytest @pytest.fixture def user(): """Create a test user.""" return User(name="Test User", email="test@example.com") @pytest.fixture def authenticated_client(client, user): """Client with authenticated user.""" client.force_login(user) return client # Fixture with teardown @pytest.fixture def temp_file(): path = Path("/tmp/test_file.txt") path.write_text("test content") yield path # Test runs here path.unlink() # Cleanup after test # Fixture scopes @pytest.fixture(scope="module") # Once per module @pytest.fixture(scope="class") # Once per class @pytest.fixture(scope="session") # Once per test session
python# tests/conftest.py - Shared fixtures import pytest @pytest.fixture def db_session(): session = create_session() yield session session.rollback() @pytest.fixture def api_client(): return TestClient(app)
pythonfrom unittest.mock import patch, MagicMock class TestPaymentService: def test_process_payment_success(self): with patch("services.payment.stripe_client") as mock_stripe: mock_stripe.charge.return_value = {"id": "ch_123", "status": "succeeded"} result = process_payment(amount=100) assert result["status"] == "succeeded" mock_stripe.charge.assert_called_once_with(amount=100) def test_process_payment_failure(self): with patch("services.payment.stripe_client") as mock_stripe: mock_stripe.charge.side_effect = PaymentError("Card declined") with pytest.raises(PaymentError): process_payment(amount=100) # MagicMock for complex objects def test_with_mock_object(): mock_user = MagicMock() mock_user.id = "user-123" mock_user.name = "Test User" mock_user.is_active = True result = get_user_info(mock_user) assert result["name"] == "Test User"
python@pytest.mark.parametrize("input,expected", [ ("hello", "HELLO"), ("world", "WORLD"), ("pytest", "PYTEST"), ]) def test_uppercase(input, expected): assert input.upper() == expected @pytest.mark.parametrize("email,is_valid", [ ("user@example.com", True), ("invalid-email", False), ("", False), ("user@.com", False), ]) def test_email_validation(email, is_valid): assert validate_email(email) == is_valid
python# pytest.ini or pyproject.toml [tool.pytest.ini_options] markers = [ "slow: marks tests as slow", "integration: marks integration tests", ] # Usage @pytest.mark.slow def test_large_data_processing(): ... @pytest.mark.integration def test_database_connection(): ... @pytest.mark.skip(reason="Not implemented yet") def test_future_feature(): ... @pytest.mark.skipif(sys.platform == "win32", reason="Unix only") def test_unix_specific(): ... # Run specific markers # pytest -m "not slow" # pytest -m "integration"
pythonimport pytest @pytest.mark.asyncio async def test_async_function(): result = await async_fetch_data() assert result is not None
bashpytest # Run all tests pytest -v # Verbose output pytest -x # Stop on first failure pytest -k "test_user" # Filter by name pytest -m "not slow" # Filter by marker pytest --cov=src # With coverage pytest -n auto # Parallel (pytest-xdist) pytest --tb=short # Short traceback
For general pytest documentation, see:
For Prowler SDK testing with provider-specific patterns (moto, MagicMock), see:
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 5,235 | 5,337 | +2% | 1 | 1 | 0% | 1,325 | 2,444 | +84% | 0 | 0 | — |
case-02 | pass→pass | 2,962 | 2,107 | -29% | 1 | 1 | 0% | 664 | 1,669 | +151% | 0 | 0 | — |
case-03 | pass→pass | 3,898 | 2,582 | -34% | 1 | 1 | 0% | 821 | 1,853 | +126% | 0 | 0 | — |
case-04 | pass→pass | 2,800 | 2,147 | -23% | 1 | 1 | 0% | 744 | 1,818 | +144% | 0 | 0 | — |
case-05 | pass→pass | 1,970 | 1,328 | -33% | 1 | 1 | 0% | 339 | 1,510 | +345% | 0 | 0 | — |
case-06 | pass→pass | 5,738 | 1,748 | -70% | 1 | 1 | 0% | 1,341 | 1,641 | +22% | 0 | 0 | — |
case-07 | pass→pass | 2,370 | 1,872 | -21% | 1 | 1 | 0% | 422 | 1,618 | +283% | 0 | 0 | — |
case-08 | pass→pass | 2,856 | 1,642 | -43% | 1 | 1 | 0% | 609 | 1,600 | +163% | 0 | 0 | — |
case-09 | pass→pass | 4,895 | 3,736 | -24% | 1 | 1 | 0% | 1,117 | 2,001 | +79% | 0 | 0 | — |
case-10 | pass→pass | 6,139 | 3,946 | -36% | 1 | 1 | 0% | 1,555 | 2,130 | +37% | 0 | 0 | — |
case-11 | pass→pass | 6,323 | 2,733 | -57% | 1 | 1 | 0% | 1,474 | 1,902 | +29% | 0 | 0 | — |
case-12 | pass→pass | 1,772 | 1,264 | -29% | 1 | 1 | 0% | 332 | 1,490 | +349% | 0 | 0 | — |
case-13 | pass→pass | 3,968 | 2,144 | -46% | 1 | 1 | 0% | 851 | 1,602 | +88% | 0 | 0 | — |
case-14 | pass→pass | 1,726 | 1,986 | +15% | 1 | 1 | 0% | 313 | 1,654 | +428% | 0 | 0 | — |
case-15 | pass→pass | 6,118 | 3,676 | -40% | 1 | 1 | 0% | 1,443 | 2,038 | +41% | 0 | 0 | — |
case-16 | pass→pass | 2,463 | 1,348 | -45% | 1 | 1 | 0% | 424 | 1,570 | +270% | 0 | 0 | — |
case-17 | fail→pass | 5,792 | 4,049 | -30% | 1 | 1 | 0% | 1,469 | 2,214 | +51% | 0 | 0 | — |
case-18 | pass→pass | 2,718 | 1,737 | -36% | 1 | 1 | 0% | 454 | 1,563 | +244% | 0 | 0 | — |
case-19 | pass→pass | 6,633 | 3,897 | -41% | 1 | 1 | 0% | 1,414 | 2,110 | +49% | 0 | 0 | — |
case-20 | pass→pass | 6,990 | 3,609 | -48% | 1 | 1 | 0% | 1,457 | 1,931 | +33% | 0 | 0 | — |
case-21 | pass→pass | 2,700 | 2,097 | -22% | 1 | 1 | 0% | 534 | 1,759 | +229% | 0 | 0 | — |
case-22 | pass→pass | 3,243 | 2,186 | -33% | 1 | 1 | 0% | 710 | 1,667 | +135% | 0 | 0 | — |
case-23 | pass→pass | 2,481 | 1,729 | -30% | 1 | 1 | 0% | 434 | 1,573 | +262% | 0 | 0 | — |
case-24 | pass→pass | 7,080 | 5,380 | -24% | 1 | 1 | 0% | 1,624 | 2,566 | +58% | 0 | 0 | — |
case-25 | pass→pass | 4,180 | 2,607 | -38% | 1 | 1 | 0% | 824 | 1,821 | +121% | 0 | 0 | — |
case-26 | pass→pass | 8,908 | 6,318 | -29% | 1 | 1 | 0% | 2,095 | 2,748 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 26 cases were attempted. The headline lift of +4 percentage points is the difference between those two pass rates over the 26 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.