josavicentevw

testing

0
0
# Install this skill:
npx skills add josavicentevw/ai-agent-skills --skill "testing"

Install specific skill from multi-skill repository

# Description

Create and execute comprehensive testing strategies including unit tests, integration tests, test-driven development, and test coverage analysis. Use when writing tests, implementing TDD, checking test coverage, or when user mentions testing, test cases, or test automation.

# SKILL.md


name: testing
description: Create and execute comprehensive testing strategies including unit tests, integration tests, test-driven development, and test coverage analysis. Use when writing tests, implementing TDD, checking test coverage, or when user mentions testing, test cases, or test automation.


Testing

A comprehensive testing skill that helps design, implement, and maintain effective test suites across different testing levels and frameworks.

Quick Start

Basic testing workflow:

# Identify testable components
# Design test cases
# Implement tests following patterns
# Run tests and verify coverage
# Maintain and refactor tests

Core Capabilities

1. Test Strategy Design

Plan comprehensive testing approaches:

  • Test Pyramid: Balance unit, integration, and E2E tests
  • Test Coverage: Identify what needs testing
  • Test Data: Design test fixtures and mocks
  • Test Environments: Configure test infrastructure
  • CI/CD Integration: Automate test execution

2. Unit Testing

Test individual components in isolation:

  • Function Tests: Test pure functions and methods
  • Class Tests: Test class behavior and state
  • Mock Dependencies: Isolate units from dependencies
  • Edge Cases: Test boundary conditions
  • Error Handling: Test exception paths

3. Integration Testing

Test component interactions:

  • API Testing: Test HTTP endpoints and responses
  • Database Testing: Test data persistence and queries
  • Service Integration: Test service-to-service communication
  • External Dependencies: Test third-party integrations
  • Contract Testing: Verify API contracts

4. Test-Driven Development (TDD)

Follow the TDD red-green-refactor cycle:

  1. Red: Write failing test first
  2. Green: Write minimal code to pass
  3. Refactor: Improve code while keeping tests green

5. Test Quality

Ensure high-quality tests:

  • Readability: Clear test names and structure
  • Maintainability: DRY principles, shared fixtures
  • Performance: Fast test execution
  • Reliability: Deterministic, no flaky tests
  • Independence: Tests don't depend on each other

Testing Patterns

AAA Pattern (Arrange-Act-Assert)

def test_calculate_total_with_tax():
    # Arrange
    items = [
        {'name': 'Item 1', 'price': 10.0},
        {'name': 'Item 2', 'price': 20.0}
    ]
    tax_rate = 0.1

    # Act
    result = calculate_total(items, tax_rate)

    # Assert
    assert result == 33.0

Given-When-Then (BDD Style)

def test_user_registration():
    # Given a new user with valid credentials
    user_data = {
        'email': '[email protected]',
        'password': 'SecurePass123!',
        'name': 'Test User'
    }

    # When registering the user
    response = register_user(user_data)

    # Then user is created and confirmation email is sent
    assert response.status_code == 201
    assert response.data['id'] is not None
    assert mock_email_service.send.called

Test Fixtures

import pytest

@pytest.fixture
def sample_user():
    """Provide a sample user for testing."""
    return {
        'id': 1,
        'email': '[email protected]',
        'name': 'Test User'
    }

@pytest.fixture
def database_session():
    """Provide a clean database session for each test."""
    session = create_test_session()
    yield session
    session.rollback()
    session.close()

def test_create_user(database_session, sample_user):
    # Use fixtures in test
    user = User(**sample_user)
    database_session.add(user)
    database_session.commit()

    assert user.id is not None

Testing by Framework

React + TypeScript (Jest + React Testing Library)

  • Component Testing: Renderizado, interacciones, estados
  • Hook Testing: Custom hooks con renderHook
  • Event Testing: Clicks, inputs, formularios
  • Async Testing: waitFor, findBy queries
  • Mock Testing: jest.fn(), jest.mock()

Angular (Jasmine/Karma)

  • Component Testing: TestBed, fixtures, change detection
  • Service Testing: HttpClientTestingModule, mock HttpClient
  • Dependency Injection: TestBed.inject()
  • Async Testing: fakeAsync, tick, flush
  • Integration Testing: RouterTestingModule, componentes integrados

Python (pytest + FastAPI)

  • Unit Testing: pytest fixtures, parametrize
  • API Testing: TestClient, async testing
  • Mock Testing: unittest.mock, pytest-mock
  • Database Testing: fixtures con rollback
  • Integration Testing: test containers, real databases

Java + Spring Boot (JUnit 5 + Mockito)

  • Unit Testing: @ExtendWith, @Mock, @InjectMocks
  • Service Testing: Mockito mocks, verify, when
  • Integration Testing: @SpringBootTest, @AutoConfigureTestDatabase
  • REST API Testing: MockMvc, TestRestTemplate
  • Transaction Testing: @Transactional en tests

Kotlin (JUnit 5 + MockK + Coroutines)

  • Coroutine Testing: runTest, runBlocking
  • Mock Testing: mockk, coEvery, coVerify
  • Flow Testing: turbine, toList()
  • Suspend Function Testing: TestCoroutineDispatcher
  • Integration Testing: Spring Boot con coroutines

Nota: Para ejemplos completos específicos de tu stack tecnológico (React, Angular, Python, Java, Kotlin), consulta EXAMPLES_STACK.md.

Ejemplos Genéricos (Legacy)

Python (pytest)

import { UserService } from './user-service';
import { EmailService } from './email-service';

// Mock the email service
jest.mock('./email-service');

describe('UserService', () => {
  let userService;
  let emailService;

  beforeEach(() => {
    // Reset mocks before each test
    jest.clearAllMocks();

    // Create instances
    emailService = new EmailService();
    userService = new UserService(emailService);
  });

  describe('createUser', () => {
    it('should create user and send welcome email', async () => {
      // Arrange
      const userData = {
        email: '[email protected]',
        name: 'Test User'
      };

      // Act
      const user = await userService.createUser(userData);

      // Assert
      expect(user.email).toBe('[email protected]');
      expect(user.name).toBe('Test User');
      expect(emailService.sendWelcome).toHaveBeenCalledWith(user);
      expect(emailService.sendWelcome).toHaveBeenCalledTimes(1);
    });

    it('should throw error for duplicate email', async () => {
      // Arrange
      const userData = { email: '[email protected]', name: 'User' };

      // Act & Assert
      await expect(userService.createUser(userData))
        .rejects
        .toThrow('Email already exists');
    });

    it.each([
      ['not-an-email'],
      ['@example.com'],
      ['test@'],
      [''],
    ])('should throw error for invalid email: %s', async (invalidEmail) => {
      const userData = { email: invalidEmail, name: 'User' };

      await expect(userService.createUser(userData))
        .rejects
        .toThrow('Invalid email format');
    });
  });

  describe('getUserById', () => {
    it('should return user when found', async () => {
      const userId = '123';
      const expectedUser = { id: userId, name: 'Test User' };

      const user = await userService.getUserById(userId);

      expect(user).toEqual(expectedUser);
    });

    it('should return null when user not found', async () => {
      const userId = 'nonexistent';

      const user = await userService.getUserById(userId);

      expect(user).toBeNull();
    });
  });
});

Java (JUnit 5)

import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class UserServiceTest {

    @Mock
    private EmailService emailService;

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @BeforeEach
    void setUp() {
        MockitoAnnotations.openMocks(this);
    }

    @Test
    @DisplayName("Should create user and send welcome email")
    void testCreateUserSuccess() {
        // Arrange
        UserDTO userData = new UserDTO("[email protected]", "Test User");
        User expectedUser = new User(1L, "[email protected]", "Test User");

        when(userRepository.existsByEmail(userData.getEmail()))
            .thenReturn(false);
        when(userRepository.save(any(User.class)))
            .thenReturn(expectedUser);

        // Act
        User result = userService.createUser(userData);

        // Assert
        assertNotNull(result);
        assertEquals("[email protected]", result.getEmail());
        assertEquals("Test User", result.getName());

        verify(userRepository).existsByEmail("[email protected]");
        verify(userRepository).save(any(User.class));
        verify(emailService).sendWelcome(expectedUser);
    }

    @Test
    @DisplayName("Should throw exception for duplicate email")
    void testCreateUserDuplicateEmail() {
        // Arrange
        UserDTO userData = new UserDTO("[email protected]", "User");
        when(userRepository.existsByEmail(userData.getEmail()))
            .thenReturn(true);

        // Act & Assert
        assertThrows(DuplicateEmailException.class, () -> {
            userService.createUser(userData);
        });

        verify(userRepository).existsByEmail("[email protected]");
        verify(userRepository, never()).save(any(User.class));
        verify(emailService, never()).sendWelcome(any(User.class));
    }

    @ParameterizedTest
    @ValueSource(strings = {"not-an-email", "@example.com", "test@", ""})
    @DisplayName("Should throw exception for invalid email")
    void testCreateUserInvalidEmail(String invalidEmail) {
        UserDTO userData = new UserDTO(invalidEmail, "User");

        assertThrows(InvalidEmailException.class, () -> {
            userService.createUser(userData);
        });
    }

    @Nested
    @DisplayName("GetUserById tests")
    class GetUserByIdTests {

        @Test
        @DisplayName("Should return user when found")
        void testGetUserByIdFound() {
            Long userId = 1L;
            User expectedUser = new User(userId, "[email protected]", "Test User");

            when(userRepository.findById(userId))
                .thenReturn(Optional.of(expectedUser));

            Optional<User> result = userService.getUserById(userId);

            assertTrue(result.isPresent());
            assertEquals(expectedUser, result.get());
        }

        @Test
        @DisplayName("Should return empty when user not found")
        void testGetUserByIdNotFound() {
            Long userId = 999L;

            when(userRepository.findById(userId))
                .thenReturn(Optional.empty());

            Optional<User> result = userService.getUserById(userId);

            assertFalse(result.isPresent());
        }
    }
}

Integration Testing

API Integration Test (Python)

import pytest
from fastapi.testclient import TestClient
from myapp import app, database

@pytest.fixture(scope="module")
def test_client():
    """Create a test client for the API."""
    client = TestClient(app)
    yield client

@pytest.fixture(scope="function")
def clean_database():
    """Provide a clean database for each test."""
    database.create_all()
    yield
    database.drop_all()

class TestUserAPI:
    """Integration tests for User API endpoints."""

    def test_create_user_endpoint(self, test_client, clean_database):
        """Test POST /users creates a new user."""
        # Arrange
        user_data = {
            "email": "[email protected]",
            "name": "Test User",
            "password": "SecurePass123!"
        }

        # Act
        response = test_client.post("/users", json=user_data)

        # Assert
        assert response.status_code == 201
        data = response.json()
        assert data["email"] == "[email protected]"
        assert data["name"] == "Test User"
        assert "id" in data
        assert "password" not in data  # Password should not be returned

    def test_get_user_endpoint(self, test_client, clean_database):
        """Test GET /users/{id} retrieves user."""
        # Arrange - create a user first
        user_data = {"email": "[email protected]", "name": "Test User"}
        create_response = test_client.post("/users", json=user_data)
        user_id = create_response.json()["id"]

        # Act
        response = test_client.get(f"/users/{user_id}")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert data["id"] == user_id
        assert data["email"] == "[email protected]"

    def test_list_users_pagination(self, test_client, clean_database):
        """Test GET /users with pagination."""
        # Arrange - create multiple users
        for i in range(15):
            test_client.post("/users", json={
                "email": f"user{i}@example.com",
                "name": f"User {i}"
            })

        # Act
        response = test_client.get("/users?page=1&per_page=10")

        # Assert
        assert response.status_code == 200
        data = response.json()
        assert len(data["items"]) == 10
        assert data["total"] == 15
        assert data["page"] == 1

Test Coverage

Aim for comprehensive coverage:

# Generate coverage report
pytest --cov=myapp --cov-report=html tests/

# Coverage goals
# - Overall: > 80%
# - Critical paths: 100%
# - New code: 100%

Focus coverage on:
- Business logic
- Error handling
- Edge cases
- Security-critical code

Best Practices

  1. Test Naming: Use descriptive names that explain what's being tested
  2. One Assertion Per Test: Focus each test on one behavior
  3. Test Independence: Tests should not depend on execution order
  4. Fast Tests: Keep unit tests under 100ms
  5. Avoid Logic in Tests: Tests should be simple and obvious
  6. Use Fixtures: Share setup code via fixtures
  7. Mock External Dependencies: Isolate unit tests
  8. Test Error Cases: Don't just test the happy path
  9. Keep Tests Maintainable: Apply same quality standards as production code
  10. Test Behavior, Not Implementation: Test what, not how

When to Use This Skill

Use this skill when:
- Writing new tests for existing code
- Implementing TDD for new features
- Improving test coverage
- Designing test strategies
- Setting up testing infrastructure
- Debugging failing tests
- Refactoring test code
- Creating test fixtures and mocks
- Implementing integration tests
- Setting up CI/CD test pipelines

Examples

See EXAMPLES.md for complete testing examples across different scenarios and frameworks.

For test templates, see templates/.

For test utilities and helpers, see scripts/test_helpers.py.

# Supported AI Coding Agents

This skill is compatible with the SKILL.md standard and works with all major AI coding agents:

Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.