Communitygithub.com

python-testing

Estrategias de pruebas en Python usando pytest, metodología TDD, fixtures, mocking, parametrización y requisitos de cobertura. Útil al escribir pruebas pytest: fixtures, mocks, parametrización o cobertura.

¿Qué es python-testing?

Este skill proporciona una guía completa sobre estrategias de pruebas para aplicaciones Python utilizando pytest, la metodología TDD y las mejores prácticas. Está diseñado para desarrolladores que trabajan con Python y necesitan escribir pruebas unitarias, de integración o end-to-end. El skill cubre desde los fundamentos de pytest, como aserciones y estructura básica de pruebas, hasta temas avanzados como fixtures con diferentes alcances (function, module, session), parametrización de pruebas con IDs legibles, mocking de funciones y propiedades usando `unittest.mock`, pruebas de código asíncrono con pytest-asyncio, y manejo de excepciones. También incluye consejos sobre organización de archivos de prueba y configuración de cobertura mínima del 80%. Es compatible con cualquier proyecto Python que use pytest y es ideal para equipos que adoptan TDD o desean mejorar la calidad de su código.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/python-testing

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Patrones de Pruebas en Python

Estrategias integrales de pruebas para aplicaciones Python usando pytest, metodología TDD y mejores prácticas.

Cuándo Activar

  • Escribir código nuevo en Python (seguir TDD: rojo, verde, refactorizar)
  • Diseñar conjuntos de pruebas para proyectos Python
  • Revisar cobertura de pruebas en Python
  • Configurar infraestructura de pruebas

Filosofía Central de Pruebas

Desarrollo Guiado por Pruebas (TDD)

Siempre sigue el ciclo TDD:

  1. ROJO: Escribe una prueba fallida para el comportamiento deseado
  2. VERDE: Escribe el código mínimo para que la prueba pase
  3. REFACTORIZAR: Mejora el código mientras mantienes las pruebas en verde
# Paso 1: Escribir prueba fallida (ROJO)
def test_sumar_numeros():
    resultado = sumar(2, 3)
    assert resultado == 5

# Paso 2: Escribir implementación mínima (VERDE)
def sumar(a, b):
    return a + b

# Paso 3: Refactorizar si es necesario (REFACTORIZAR)

Requisitos de Cobertura

  • Objetivo: 80%+ de cobertura de código
  • Rutas críticas: Se requiere cobertura del 100%
  • Usa pytest --cov para medir la cobertura
pytest --cov=mipaquete --cov-report=term-missing --cov-report=html

Fundamentos de pytest

Estructura Básica de Pruebas

import pytest

def test_suma():
    """Prueba suma básica."""
    assert 2 + 2 == 4

def test_mayusculas():
    """Prueba conversión a mayúsculas."""
    texto = "hola"
    assert texto.upper() == "HOLA"

def test_agregar_lista():
    """Prueba agregar a lista."""
    items = [1, 2, 3]
    items.append(4)
    assert 4 in items
    assert len(items) == 4

Aserciones

# Igualdad
assert resultado == esperado

# Desigualdad
assert resultado != inesperado

# Veracidad
assert resultado  # Verdadero
assert not resultado  # Falso
assert result is True  # Exactamente True
assert result is False  # Exactamente False
assert result is None  # Exactamente None

# Pertenencia
assert elemento in colección
assert elemento not in colección

# Comparaciones
assert resultado > 0
assert 0 <= resultado <= 100

# Verificación de tipo
assert isinstance(resultado, str)

# Prueba de excepciones (enfoque preferido)
with pytest.raises(ValueError):
    raise ValueError("mensaje de error")

# Verificar mensaje de excepción
with pytest.raises(ValueError, match="entrada inválida"):
    raise ValueError("entrada inválida proporcionada")

# Verificar atributos de excepción
with pytest.raises(ValueError) as exc_info:
    raise ValueError("mensaje de error")
assert str(exc_info.value) == "mensaje de error"

Fixtures

Uso Básico de Fixtures

import pytest

@pytest.fixture
def datos_muestra():
    """Fixture que proporciona datos de muestra."""
    return {"nombre": "Alice", "edad": 30}

def test_datos_muestra(datos_muestra):
    """Prueba usando el fixture."""
    assert datos_muestra["nombre"] == "Alice"
    assert datos_muestra["edad"] == 30

Fixture con Configuración/Limpieza

@pytest.fixture
def base_datos():
    """Fixture con configuración y limpieza."""
    # Configuración
    db = Database(":memory:")
    db.create_tables()
    db.insert_test_data()

    yield db  # Proporcionar a la prueba

    # Limpieza
    db.close()

def test_consulta_base_datos(base_datos):
    """Prueba operaciones de base de datos."""
    resultado = base_datos.query("SELECT * FROM usuarios")
    assert len(resultado) > 0

Alcances de Fixtures

# Alcance de función (predeterminado) - se ejecuta para cada prueba
@pytest.fixture
def archivo_temporal():
    with open("temp.txt", "w") as f:
        yield f
    os.remove("temp.txt")

# Alcance de módulo - se ejecuta una vez por módulo
@pytest.fixture(scope="module")
def db_modulo():
    db = Database(":memory:")
    db.create_tables()
    yield db
    db.close()

# Alcance de sesión - se ejecuta una vez por sesión de prueba
@pytest.fixture(scope="session")
def recurso_compartido():
    recurso = RecursoCostoso()
    yield recurso
    recurso.limpiar()

Fixture con Parámetros

@pytest.fixture(params=[1, 2, 3])
def numero(request):
    """Fixture parametrizado."""
    return request.param

def test_numeros(numero):
    """Prueba se ejecuta 3 veces, una para cada parámetro."""
    assert numero > 0

Usando Múltiples Fixtures

@pytest.fixture
def usuario():
    return User(id=1, nombre="Alice")

@pytest.fixture
def admin():
    return User(id=2, nombre="Admin", rol="admin")

def test_interaccion_usuario_admin(usuario, admin):
    """Prueba usando múltiples fixtures."""
    assert admin.puede_gestionar(usuario)

Fixtures Automáticos

@pytest.fixture(autouse=True)
def restablecer_config():
    """Se ejecuta automáticamente antes de cada prueba."""
    Config.restablecer()
    yield
    Config.limpiar()

def test_sin_llamar_fixture():
    # restablecer_config se ejecuta automáticamente
    assert Config.obtener_config("debug") is False

Conftest.py para Fixtures Compartidos

# tests/conftest.py
import pytest

@pytest.fixture
def cliente():
    """Fixture compartido para todas las pruebas."""
    app = crear_app(pruebas=True)
    with app.test_client() as client:
        yield client

@pytest.fixture
def encabezados_auth(cliente):
    """Genera encabezados de autenticación para pruebas de API."""
    respuesta = cliente.post("/api/login", json={
        "usuario": "test",
        "contraseña": "test"
    })
    token = respuesta.json["token"]
    return {"Authorization": f"Bearer {token}"}

Parametrización

Parametrización Básica

@pytest.mark.parametrize("entrada,esperado", [
    ("hola", "HOLA"),
    ("mundo", "MUNDO"),
    ("PyThOn", "PYTHON"),
])
def test_mayusculas(entrada, esperado):
    """Prueba se ejecuta 3 veces con diferentes entradas."""
    assert entrada.upper() == esperado

Múltiples Parámetros

@pytest.mark.parametrize("a,b,esperado", [
    (2, 3, 5),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_sumar(a, b, esperado):
    """Prueba suma con múltiples entradas."""
    assert sumar(a, b) == esperado

Parametrización con IDs

@pytest.mark.parametrize("entrada,esperado", [
    ("[email protected]", True),
    ("invalido", False),
    ("@sin-dominio.com", False),
], ids=["email-valido", "sin-arroba", "sin-dominio"])
def test_validacion_email(entrada, esperado):
    """Prueba validación de email con IDs legibles."""
    assert es_email_valido(entrada) is esperado

Fixtures Parametrizados

@pytest.fixture(params=["sqlite", "postgresql", "mysql"])
def db(request):
    """Prueba contra múltiples motores de base de datos."""
    if request.param == "sqlite":
        return Database(":memory:")
    elif request.param == "postgresql":
        return Database("postgresql://localhost/test")
    elif request.param == "mysql":
        return Database("mysql://localhost/test")

def test_operaciones_base_datos(db):
    """Prueba se ejecuta 3 veces, una para cada base de datos."""
    resultado = db.query("SELECT 1")
    assert resultado is not None

Marcadores y Selección de Pruebas

Marcadores Personalizados

# Marcar pruebas lentas
@pytest.mark.lenta
def test_operacion_lenta():
    time.sleep(5)

# Marcar pruebas de integración
@pytest.mark.integracion
def test_integracion_api():
    response = requests.get("https://api.example.com")
    assert response.status_code == 200

# Marcar pruebas unitarias
@pytest.mark.unitaria
def test_logica_unitaria():
    assert calcular(2, 3) == 5

Ejecutar Pruebas Específicas

# Ejecutar solo pruebas rápidas
pytest -m "not lenta"

# Ejecutar solo pruebas de integración
pytest -m integracion

# Ejecutar pruebas de integración o lentas
pytest -m "integracion or lenta"

# Ejecutar pruebas marcadas como unitarias pero no lentas
pytest -m "unitaria and not lenta"

Configurar Marcadores en pytest.ini

[pytest]
markers =
    lenta: marca las pruebas como lentas
    integracion: marca las pruebas como pruebas de integración
    unitaria: marca las pruebas como pruebas unitarias
    django: marca las pruebas como que requieren Django

Mocking y Patching

Mocking de Funciones

from unittest.mock import patch, Mock

@patch("mypackage.llamada_api_externa")
def test_con_mock(llamada_api_mock):
    """Prueba con API externa simulada."""
    llamada_api_mock.return_value = {"estado": "éxito"}

    resultado = mi_funcion()

    llamada_api_mock.assert_called_once()
    assert resultado["estado"] == "éxito"

Mocking de Valores de Retorno

@patch("mypackage.Database.conectar")
def test_conexion_base_datos(conectar_mock):
    """Prueba con conexión de base de datos simulada."""
    conectar_mock.return_value = MockConexion()

    db = Database()
    db.conectar()

    conectar_mock.assert_called_once_with("localhost")

Mocking de Excepciones

@patch("mypackage.llamada_api")
def test_manejo_error_api(llamada_api_mock):
    """Prueba manejo de errores con excepción simulada."""
    llamada_api_mock.side_effect = ConnectionError("Error de red")

    with pytest.raises(ConnectionError):
        llamada_api()

    llamada_api_mock.assert_called_once()

Mocking de Administradores de Contexto

@patch("builtins.open", new_callable=mock_open)
def test_lectura_archivo(archivo_mock):
    """Prueba lectura de archivo con open simulado."""
    archivo_mock.return_value.read.return_value = "contenido del archivo"

    resultado = leer_archivo("test.txt")

    archivo_mock.assert_called_once_with("test.txt", "r")
    assert resultado == "contenido del archivo"

Usando Autospec

@patch("mypackage.DBConnection", autospec=True)
def test_autospec(db_mock):
    """Prueba con autospec para detectar mal uso de API."""
    db = db_mock.return_value
    db.query("SELECT * FROM usuarios")

    # Esto fallaría si DBConnection no tiene el método query
    db_mock.assert_called_once()

Mock de Instancias de Clase

class TestUserService:
    @patch("mypackage.UserRepository")
    def test_crear_usuario(self, repo_mock):
        """Prueba creación de usuario con repositorio simulado."""
        repo_mock.return_value.guardar.return_value = User(id=1, nombre="Alice")

        servicio = UserService(repo_mock.return_value)
        usuario = servicio.crear_usuario(nombre="Alice")

        assert usuario.nombre == "Alice"
        repo_mock.return_value.guardar.assert_called_once()

Mock de Propiedades

@pytest.fixture
def config_mock():
    """Crear un mock con una propiedad."""
    config = Mock()
    type(config).debug = PropertyMock(return_value=True)
    type(config).api_key = PropertyMock(return_value="test-key")
    return config

def test_con_config_mock(config_mock):
    """Prueba con propiedades de configuración simuladas."""
    assert config_mock.debug is True
    assert config_mock.api_key == "test-key"

Pruebas de Código Asíncrono

Pruebas Asíncronas con pytest-asyncio

import pytest

@pytest.mark.asyncio
async def test_funcion_asincrona():
    """Prueba función asíncrona."""
    resultado = await sumar_asincrono(2, 3)
    assert resultado == 5

@pytest.mark.asyncio
async def test_asincrono_con_fixture(cliente_asincrono):
    """Prueba asíncrona con fixture asíncrono."""
    respuesta = await cliente_asincrono.get("/api/usuarios")
    assert respuesta.status_code == 200

Fixture Asíncrono

@pytest.fixture
async def cliente_asincrono():
    """Fixture asíncrono que proporciona un cliente de prueba asíncrono."""
    app = crear_app()
    async with app.test_client() as client:
        yield client

@pytest.mark.asyncio
async def test_endpoint_api(cliente_asincrono):
    """Prueba usando fixture asíncrono."""
    respuesta = await cliente_asincrono.get("/api/datos")
    assert respuesta.status_code == 200

Mocking de Funciones Asíncronas

@pytest.mark.asyncio
@patch("mypackage.llamada_api_asincrona")
async def test_mock_asincrono(llamada_api_mock):
    """Prueba función asíncrona con mock."""
    llamada_api_mock.return_value = {"estado": "ok"}

    resultado = await mi_funcion_asincrona()

    llamada_api_mock.assert_awaited_once()
    assert resultado["estado"] == "ok"

Pruebas de Excepciones

Pruebas de Excepciones Esperadas

def test_dividir_por_cero():
    """Prueba que dividir por cero lanza ZeroDivisionError."""
    with pytest.raises(ZeroDivisionError):
        dividir(10, 0)

def test_excepcion_personalizada():
    """Prueba excepción personalizada con mensaje."""
    with pytest.raises(ValueError, match="entrada inválida"):
        validar_entrada("inválido")

Pruebas de Atributos de Excepción

def test_excepcion_con_detalles():
    """Prueba excepción con atributos personalizados."""
    with pytest.raises(ErrorPersonalizado) as exc_info:
        raise ErrorPersonalizado("error", codigo=400)

    assert exc_info.value.codigo == 400
    assert "error" in str(exc_info.value)

Pruebas de Efectos Secundarios

Pruebas de Operaciones con Archivos

import tempfile
import os

def test_procesamiento_archivo():
    """Prueba procesamiento de archivo con archivo temporal."""
    with tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') as f:
        f.write("contenido de prueba")
        ruta_temp = f.name

    try:
        resultado = procesar_archivo(ruta_temp)
        assert resultado == "procesado: contenido de prueba"
    finally:
        os.unlink(ruta_temp)

Pruebas con Fixture tmp_path de pytest

def test_con_ruta_temporal(tmp_path):
    """Prueba usando el fixture de ruta temporal incorporado de pytest."""
    archivo_prueba = tmp_path / "test.txt"
    archivo_prueba.write_text("hola mundo")

    resultado = procesar_archivo(str(archivo_prueba))
    assert resultado == "hola mundo"
    # tmp_path se limpia automáticamente

Pruebas con Fixture tmpdir

def test_con_tmpdir(tmpdir):
    """Prueba usando el fixture tmpdir de pytest."""
    archivo_prueba = tmpdir.join("test.txt")
    archivo_prueba.write("datos")

    resultado = procesar_archivo(str(archivo_prueba))
    assert resultado == "datos"

Organización de Pruebas

Estructura de Directorios

tests/
├── conftest.py                 # Fixtures compartidos
├── __init__.py
├── unit/                       # Pruebas unitarias
│   ├── __init__.py
│   ├── test_modelos.py
│   ├── test_utilidades.py
│   └── test_servicios.py
├── integration/                # Pruebas de integración
│   ├── __init__.py
│   ├── test_api.py
│   └── test_base_datos.py
└── e2e/                        # Pruebas de extremo a extremo
    ├── __init__.py
    └── test_flujo_usuario.py

Clases de Prueba

class TestUserService:
    """Agrupa pruebas relacionadas en una clase."""

    @pytest.fixture(autouse=True)
    def configurar(self):
        """Configuración que se ejecuta antes de cada prueba en esta clase."""
        self.servicio = UserService()

    def test_crear_usuario(self):
        """Prueba creación de usuario."""
        usuario = self.servicio.crear_usuario("Alice")
        assert usuario.nombre == "Alice"

    def test_eliminar_usuario(self):
        """Prueba eliminación de usuario."""
        usuario = self.servicio.crear_usuario("Bob")
        self.servicio.eliminar_usuario(usuario.id)
        assert self.servicio.obtener_usuario(usuario.id) is None

Mejores Prácticas

Nomenclatura de Pruebas

# Nombres descriptivos de funciones de prueba
def test_calcular_impuesto_con_ingreso_positivo():
    """Prueba con nombre descriptivo."""
    pass

def test_validar_email_con_formato_valido():
    """Prueba con nombre descriptivo."""
    pass

def test_manejar_excepcion_base_datos():
    """Prueba con nombre descriptivo."""
    pass

Aislar Pruebas

# Cada prueba debe ser independiente
class TestOperaciones:
    def test_agregar_elemento(self):
        lista = []
        lista.append(1)
        assert len(lista) == 1

    def test_eliminar_elemento(self):
        lista = []  # Comenzar fresco
        assert len(lista) == 0

Fixture Única Responsabilidad

@pytest.fixture
def conexion_memoria():
    """Fixture para conexión de base de datos en memoria."""
    return Database(":memory:")

@pytest.fixture
def datos_prueba(conexion_memoria):
    """Fixture para datos de prueba usando conexión en memoria."""
    conexion_memoria.insertar_datos_prueba()
    return conexion_memoria

def test_consulta(datos_prueba):
    resultado = datos_prueba.query("SELECT * FROM usuarios")
    assert len(resultado) == 10

Configuración de pytest

pytest.ini

[pytest]
# Configuración de proyecto
pythonpath = src

# Marcadores
markers =
    lenta: marca las pruebas como lentas
    integracion: marca las pruebas como pruebas de integración

# Opciones por defecto
addopts = -v --tb=short --strict-markers

# Directorio de pruebas
testpaths = tests

pyproject.toml (Alternativa Moderna)

[tool.pytest.ini_options]
minversion = "7.0"
addopts = "-v --tb=short --strict-markers"
testpaths = ["tests"]
pythonpath = ["src"]

markers = [
    "lenta: marca las pruebas como lentas",
    "integracion: marca las pruebas como pruebas de integración",
]

Herramientas Adicionales

Cobertura de Código

# Ejecutar pruebas con cobertura
pytest --cov=src/

# Mostrar líneas faltantes
pytest --cov=src/ --cov-report=term-missing

# Generar informe HTML
pytest --cov=src/ --cov-report=html

# Establecer umbral de cobertura
pytest --cov=src/ --cov-fail-under=80

Perfilado de Pruebas lentas

# Listar pruebas más lentas
pytest --durations=10

# Mostrar todas las duraciones
pytest --durations=0

Ejecución en Paralelo

# Ejecutar pruebas en paralelo (requiere pytest-xdist)
pytest -n auto

# Ejecutar con 4 CPUs
pytest -n 4

Patrones Comunes

Pruebas de Fábrica

class TestModeloUsuario:
    def test_crear_usuario_valido(self, fábrica_usuario):
        usuario = fábrica_usuario(nombre="Alice")
        assert usuario.nombre == "Alice"
        assert usuario.email is not None

    def test_crear_usuario_con_email_personalizado(self, fábrica_usuario):
        usuario = fábrica_usuario(email="[email protected]")
        assert usuario.email == "[email protected]"

    def test_usuario_tiene_rol_predeterminado(self, fábrica_usuario):
        usuario = fábrica_usuario()
        assert usuario.rol == "usuario"


@pytest.fixture
def fábrica_usuario():
    def crear_usuario(**kwargs):
        datos_predeterminados = {
            "nombre": "Usuario Predeterminado",
            "email": "[email protected]",
            "rol": "usuario",
        }
        datos_predeterminados.update(kwargs)
        return User(**datos_predeterminados)
    return crear_usuario

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills relacionados