Módulo 4: Marcadores y configuración: pytest.ini y markers

1. Presentación del módulo: la capa de configuración

Descripción

Al terminar esta lección vas a entender qué problema resuelve este módulo y por qué llega justo después del anterior. En el módulo 3 le diste a la suite de Reservo una forma física: carpetas por capa —tests/unit/ y tests/integration/—, cada una con su conftest.py, y aprendiste a leer esa forma con --collect-only. Fue un gran paso: pasaste del galpón plano al supermercado con pasillos. Pero la carpeta tiene un techo, y este módulo es sobre lo que hay más allá de ese techo. El techo es este: un test vive en una sola carpeta. Elegiste ordenar por capa, y entonces unit/ guarda lo rápido y integration/ lo lento —perfecto para correr "solo lo rápido"—, pero ahora no tienes forma de pedir "solo lo crítico, sin importar en qué capa esté", porque "crítico" no es una carpeta. La carpeta es un eje, y un test solo puede estar en un punto de ese eje. Este módulo agrega el segundo eje: los marcadores, etiquetas que pegas a los tests para cortarlos por una dimensión distinta a la de las carpetas, y la capa de configuración que los gobierna.

Esto importa por una razón que se siente el día que la suite tiene dos criterios de corte que compiten. Quieres correr "lo rápido" (que es una capa) unas veces y "lo crítico" (que cruza capas) otras, y la carpeta solo te da uno de los dos gratis. Los marcadores resuelven eso: le pegas a cada test las etiquetas que le corresponden —slow, smoke, integration— y luego seleccionas cualquier combinación con -m "not slow", -m "integration and smoke", sin mover un solo archivo. Y como una etiqueta suelta es frágil —basta un typo, smoek por smoke, para que un test desaparezca de la selección en silencio—, los marcadores solo sirven de verdad cuando viven bajo un contrato: un archivo de configuración central donde los registras, activas la revisión estricta que caza los typos, y fijas cómo corre la suite para todo el equipo. Ese archivo —pyproject.toml o pytest.ini— es la capa que este módulo construye.

Conexión con el módulo: esta lección es el mapa, no el territorio. Aquí no vas a marcar ni configurar todavía; vas a ver el problema —la suite en capas que no se puede cortar por una segunda dimensión— y el orden de las seis lecciones que siguen. La lección 2 define qué es un marcador y por qué la carpeta no basta. La lección 3 diseña el vocabulario de marcadores de Reservo y muestra cómo pegarlos. La lección 4 es la que vuelve seguros los marcadores: registrarlos y activar --strict-markers para que un typo sea un error, no un silencio. La lección 5 presenta el archivo de config central y sus opciones. La lección 6 fija las opciones por defecto con addopts. La lección 7 cobra el trabajo: seleccionar subconjuntos con expresiones -m. Y la lección 8 —el mini-proyecto— te da la suite de Reservo para que la marques y le escribas su contrato de config, con la suite de humo corriendo por defecto y la lenta a demanda.

Los pasillos y las etiquetas de colores

Piénsalo así, retomando el supermercado del módulo 3. Ya tienes los pasillos: lácteos, panadería, limpieza. Cada producto vive en su pasillo, y eso te deja recorrer "solo los refrigerados" yendo a esa zona. Pero un día el gerente quiere lanzar una promoción de "productos de marca propia", y esos productos están repartidos por todos los pasillos: hay marca propia en lácteos, en limpieza, en panadería. No puedes juntarlos moviéndolos a un pasillo nuevo —cada uno tiene que quedarse en su sección para que el cliente lo encuentre donde espera—. Lo que hace el gerente es pegar una etiqueta de color en cada producto de marca propia, sin importar en qué pasillo esté. Ahora hay dos formas de recorrer la tienda: por pasillo (dónde vive el producto) y por etiqueta (una propiedad que cruza los pasillos). El pasillo no se movió; la etiqueta le sumó una segunda manera de agrupar.

Los dos sistemas conviven sin estorbarse. El pasillo responde "¿dónde está la leche?"; la etiqueta responde "¿cuáles son de marca propia?". Un producto tiene un pasillo y puede tener varias etiquetas —de marca propia, en oferta, orgánico—, y cada etiqueta es un corte distinto de la tienda entera. Con los tests pasa exactamente igual. La carpeta es el pasillo: dice dónde vive el test, y en el módulo 3 la usaste para separar rápido de lento. El marcador es la etiqueta de color: una propiedad que le pegas al test sin moverlo de su carpeta, y que te deja cortar la suite por una dimensión nueva —"lo crítico", "lo lento", "lo de integración"— que la carpeta no capturaba. Este módulo es aprender a pegar las etiquetas con criterio y a gobernarlas con un contrato, para que los dos ejes —pasillo y etiqueta— trabajen juntos.

El caso: la suite en capas que no se puede cortar de otra forma

No partimos de cero. Vienes probando Reservo —el sistema de reservas del espacio de coworking— desde fundamentos, y en el módulo 3 le diste su forma en capas. Recordemos el dominio, que es el material del módulo entero. Es lógica pura de Python, en memoria, sin base de datos ni red:

# reservo/models.py — el dominio, en centavos int (nunca float para dinero)
from dataclasses import dataclass
from datetime import datetime


@dataclass
class Room:
    id: str
    name: str            # "Focus", "Studio", "Boardroom"
    capacity: int
    hourly_cents: int    # precio por hora, EN CENTAVOS (int)


@dataclass
class Member:
    id: str
    name: str
    tier: str            # "basic" | "pro"  (pro tiene 20% de descuento)


@dataclass
class Booking:
    id: str
    room_id: str
    member_id: str
    start: datetime
    end: datetime
    status: str          # "confirmed" | "cancelled"
    price_cents: int     # lo pagado por esta reserva, EN CENTAVOS (int)

Sobre ese dominio hay dos clases de lógica, y ya las conoces del módulo 3. Está la lógica pura sin estadoprice_cents(room, member, hours) calcula un precio, refund_cents(booking, price_paid_cents, now) calcula un reembolso, overlaps(...) dice si dos horarios chocan—, que probamos en la capa unit/. Y están las piezas que trabajan juntas —el Calendar y el BookingService, que reserva y cancela de verdad—, que probamos en la capa integration/. Los números-ancla siguen siendo los de siempre: la sala Focus cuesta 2500 centavos la hora; tres horas para un miembro basic son 7500 centavos, y para un pro (20% de descuento) son 6000. El reembolso de una reserva pagada 6000: cancelando con 72 h de anticipación se devuelve 6000 (total), con 36 h se devuelve 3000 (mitad), con 12 h se devuelve 0 (nada).

La suite del módulo 3 creció un poco: hoy tiene trece tests en dos capas físicas. Así se ve su árbol, con la herramienta que ya conoces:

python3 -m pytest --collect-only

Qué esperar. En mi máquina (Python 3.14.0, pytest 9.1.1), sobre la suite en capas:

collected 13 items

<Dir m4>
  <Dir tests>
    <Dir integration>
      <Module test_booking_flow.py>
        <Function test_booking_a_room_charges_the_pro_price>
        <Function test_room_is_unavailable_after_it_is_booked>
        <Function test_double_booking_the_same_slot_is_rejected>
      <Module test_cancel_flow.py>
        <Function test_cancelling_72h_ahead_refunds_in_full>
        <Function test_cancelling_frees_the_room>
    <Dir unit>
      <Module test_overlaps.py>
        <Function test_touching_intervals_do_not_overlap>
        <Function test_nested_interval_overlaps>
      <Module test_pricing.py>
        <Function test_basic_member_pays_hourly_rate_times_hours>
        <Function test_pro_member_gets_twenty_percent_off>
        <Function test_zero_hours_costs_nothing>
      <Module test_refund.py>
        <Function test_full_refund_at_72h>
        <Function test_half_refund_at_36h>
        <Function test_no_refund_at_12h>

La forma es la del módulo 3: dos capas, integration/ y unit/, cada una con sus módulos. Y esta forma te da un corte gratis: pytest tests/unit corre los ocho rápidos, pytest tests/integration los cinco lentos. Perfecto para "corre lo rápido mientras desarrollo".

Ahora aparece una necesidad nueva que la forma no cubre. Antes de un despliegue, no quieres correr "todo lo rápido" ni "todo lo lento": quieres correr el puñado de tests críticos que confirman que Reservo no está roto de raíz —que el precio pro se cobra bien, que el reembolso total sale, que reservar una sala funciona—. Ese puñado es lo que se llama una suite de humo (smoke suite), y aquí está el problema: sus tests están repartidos entre las dos capas. "El precio pro se cobra bien" es un test unitario (test_pro_member_gets_twenty_percent_off, en unit/); "reservar una sala cobra el precio pro" es de integración (test_booking_a_room_charges_the_pro_price, en integration/). No hay una carpeta smoke/, y crearla te obligaría a mover esos tests fuera de su capa —perdiendo el corte rápido/lento que tanto te costó—. La carpeta ya está gastada en separar por naturaleza; no puede además separar por criticidad. Necesitas un segundo eje.

Ejemplo trabajado: el corte que la carpeta no da, con un marcador

Adelantemos el final para que veas a dónde vamos. Supongamos que a esos trece tests ya les pegaste etiquetas —eso es el trabajo de las lecciones 3 a 6—. En particular, a los tres flujos lentos de integración (los que arman el BookingService y tardan) les pusiste la etiqueta slow. Ahora puedes pedir "toda la suite menos lo lento" con una expresión de marcador, sin nombrar archivos ni carpetas:

python3 -m pytest -m "not slow"

Qué esperar. En mi máquina, sobre la suite marcada:

collected 13 items / 3 deselected / 10 selected

tests/integration/test_booking_flow.py ..                                [ 20%]
tests/unit/test_overlaps.py ..                                           [ 40%]
tests/unit/test_pricing.py ...                                           [ 70%]
tests/unit/test_refund.py ...                                            [100%]

======================= 10 passed, 3 deselected in 0.01s =======================

Lee la primera línea, porque es la novedad del módulo entero: collected 13 items / 3 deselected / 10 selected. Pytest coleccionó los trece, pero la expresión -m "not slow" deseleccionó tres —los tres flujos marcados slow— y corrió los otros diez. Y mira el tiempo: 0.01s. Compáralo con la suite completa, que en mi máquina tarda 1.24s porque los tres flujos lentos suman más de un segundo entre ellos. El marcador te dejó pedir un corte —"todo menos lo lento"— que la carpeta no podía darte, porque "lo lento" no estaba en una sola carpeta: dos de los tres flujos lentos viven en test_cancel_flow.py y uno en test_booking_flow.py, pero también hay tests rápidos en test_booking_flow.py. El marcador cruzó esa frontera; la carpeta no habría podido.

Fíjate también en algo que la carpeta nunca te dio: test_booking_flow.py aparece con dos tests corridos (..), no tres. Su tercer test es el flujo lento que quedó deseleccionado. El marcador cortó dentro de un archivo, seleccionando dos de sus tres tests. Ninguna carpeta puede hacer eso —una carpeta agarra el archivo entero—. Esa capacidad de cortar por debajo del archivo, transversalmente, es lo que el marcador agrega y la estructura del módulo 3 no tenía.

Ese contraste —el corte por carpeta contra el corte por etiqueta— es el hilo del módulo. Todo lo que sigue sirve para construir ese segundo eje con criterio: elegir buenas etiquetas, registrarlas para que no se corrompan con typos, y meterlas en un contrato de configuración que haga que la suite corra igual para todos.

Las dos capas que este módulo construye

Antes de recorrer las lecciones, conviene nombrar las dos ideas de fondo, porque el módulo entero es construir una encima de la otra.

Primera: los marcadores, el segundo eje de categorización. Un marcador es una etiqueta que le pegas a un test con @pytest.mark.NOMBRE@pytest.mark.slow, @pytest.mark.smoke, @pytest.mark.integration—. No cambia lo que el test hace: el test sigue verificando exactamente lo mismo. Lo único que cambia es que ahora el test lleva una etiqueta, y con esa etiqueta puedes seleccionarlo. Como cada test puede llevar varias etiquetas, y cada etiqueta es un corte distinto de la suite, los marcadores te dan tantos ejes de corte como dimensiones quieras nombrar. Esa es la potencia: la carpeta te da uno; los marcadores, todos los que necesites. Las lecciones 2 y 3 son sobre esto.

Segunda: la configuración central, el contrato que los gobierna. Los marcadores sueltos son peligrosos: pytest acepta cualquier @pytest.mark.loquesea sin chistar, así que un typo crea un marcador nuevo en silencio y tu test se cae de la selección sin aviso. La cura es un archivo de configuración centralpyproject.toml con la sección [tool.pytest.ini_options], o pytest.ini— donde registras los marcadores válidos, activas --strict-markers para que cualquier otro sea un error, y de paso fijas cómo corre la suite para todos con addopts y testpaths. Ese archivo es el contrato del framework: un solo lugar que define, para todo el equipo, qué marcadores existen y cómo se corre la suite. Las lecciones 4, 5 y 6 son sobre esto, y la 7 cobra el resultado seleccionando subconjuntos.

Aquí está el módulo entero en una tabla:

LecciónTemaLa idea en una frase
2Qué es un marcadorUna etiqueta que corta la suite por una dimensión que la carpeta no captura
3Marcadores en ReservoDiseñar el vocabulario (smoke, slow, integration) y pegarlo a los tests
4Registrar + --strict-markersUn typo de marcador debe ser un error, no un silencio
5El archivo de config centralpyproject.toml/pytest.ini: el único lugar que define cómo corre la suite
6addopts y las opciones por defectopytest a secas ya corre como el equipo acordó
7Seleccionar con -mExpresiones booleanas: not slow, integration and smoke

Lo que este módulo NO toca

Conviene marcar la frontera desde ahora, porque hay dos temas vecinos que parecen de aquí y son de módulos siguientes.

La configuración por entorno no es de este módulo. En el módulo 7 vas a aprender a hacer que la suite corra distinto según dónde esté: una config en tu máquina, otra en el servidor de integración continua, una opción --env que cambia el comportamiento. Aquí el archivo de config es único y fijo: define un comportamiento, el mismo en todas partes. Cuando en este módulo hablemos de "el contrato", queremos decir el acuerdo que vale igual para todos, no todavía la variación por entorno. Esa variación es una capa que se monta encima de lo que construyes aquí, y llega en el módulo 7.

Los plugins y hooks tampoco son de este módulo. En el módulo 6 vas a extender pytest escribiendo tus propios ganchos: una opción de línea de comandos nueva con pytest_addoption, un hook que modifica la colección, un resumen de reporte con pytest_terminal_summary. Aquí usamos los marcadores y la configuración que pytest ya trae incorporados: no escribimos ni una línea de hook. La diferencia importa —marcar un test y registrar un marcador es usar pytest; escribir un pytest_collection_modifyitems que le hace algo automático a los marcados es extender pytest—, y este módulo se queda del lado de usar.

Y dos fronteras que ya conoces. Las carpetas físicas (tests/unit/, tests/integration/) fueron el módulo 3; aquí los marcadores son un eje complementario a esas carpetas, no un reemplazo —el test sigue viviendo en su carpeta y además lleva sus etiquetas—. Y las fixtures fueron el módulo 2; aquí el conftest.py reaparece de refilón, solo como uno de los lugares donde podría vivir la config, no como tema.

Errores comunes

Creer que el marcador reemplaza a la carpeta (de concepto). Qué pasa: alguien ve que -m integration selecciona los tests de integración y concluye que las carpetas del módulo 3 sobran —"para qué tests/integration/ si tengo @pytest.mark.integration"—. Por qué pasa: los dos parecen hacer lo mismo (agrupar tests de integración), así que uno se ve redundante. Cómo detectarlo: si estás a punto de borrar la estructura de carpetas porque ya marcaste todo, estás por perder el eje que la carpeta te daba gratis —la ubicación predecible, el "dónde vive este test"—. Cómo corregirlo: son dos ejes complementarios, no dos formas del mismo eje. La carpeta responde "¿dónde está este test?" y organiza el archivo en disco; el marcador responde "¿de qué categoría transversal es?" y no mueve nada. Una suite madura usa los dos: carpetas para la estructura, marcadores para los cortes que cruzan la estructura.

Marcar antes de saber qué corte necesitas (de anticipación). Qué pasa: alguien aprende los marcadores y empieza a pegar @pytest.mark.esto y @pytest.mark.aquello a todo, inventando categorías por si acaso. Por qué pasa: marcar es fácil y se siente productivo, y "más metadata" parece siempre mejor. Cómo detectarlo: si tienes marcadores que nunca usaste en un -m —etiquetas que nadie selecciona jamás—, los pusiste sin una necesidad real. Cómo corregirlo: un marcador se justifica por un corte que de verdad quieres hacer. "Quiero correr lo lento aparte" justifica slow. "Quiero un chequeo rápido antes de desplegar" justifica smoke. Si no puedes nombrar el pytest -m X que vas a correr, no crees el marcador X todavía. La lección 3 vuelve sobre esto: pocos marcadores, cada uno con su corte.

Confiar en un marcador sin registrarlo (de disciplina). Qué pasa: alguien pega @pytest.mark.smoke a diez tests, corre pytest -m smoke, ve que funciona, y sigue —sin registrar el marcador en ninguna config—. Por qué pasa: funciona sin registrar; pytest solo tira un warning fácil de ignorar. Cómo detectarlo: si tu suite de humo un día corre menos tests de los que esperabas y no sabes por qué, probablemente un typo (smoek) creó un marcador fantasma y un test se cayó de la selección sin aviso. Cómo corregirlo: registrar los marcadores y activar --strict-markers, que es exactamente la lección 4. Un marcador sin registrar es una etiqueta que cualquier error de tipeo puede corromper en silencio; registrado y con strict, un typo es un error inmediato.

Ejercicios

Ejercicio 1 — Nombra el corte que la carpeta no da. La suite de Reservo está en capas: tests/unit/ (ocho tests rápidos) y tests/integration/ (cinco tests lentos y sociables). Para cada necesidad, di si la puedes cubrir con un comando de carpeta (pytest tests/unit, pytest tests/integration) o si necesitas un marcador, y por qué. (a) "Corre solo los tests unitarios." (b) "Corre solo los tres tests críticos que confirman que Reservo no está roto —dos de ellos unitarios, uno de integración." (c) "Corre todo menos los flujos lentos."

Ver solución
  • (a) Carpeta. "Los tests unitarios" es exactamente lo que la carpeta unit/ agrupa. pytest tests/unit los da los ocho, sin necesidad de marcador. La carpeta ya captura esta dimensión (la capa), porque fue el criterio con que la organizaste en el módulo 3.
  • (b) Marcador. Los tres tests críticos están repartidos entre las dos capas —dos en unit/, uno en integration/—, así que ninguna carpeta los contiene a todos y solo a ellos. pytest tests/unit traería los ocho unitarios (incluyendo los no críticos); pytest tests/integration traería los cinco de integración. Para juntar exactamente esos tres, sin importar su carpeta, necesitas una etiqueta transversal —un marcador smoke— y seleccionar con pytest -m smoke.
  • (c) Marcador. "Los flujos lentos" tampoco son una carpeta limpia: aunque los tres viven en integration/, esa carpeta también tiene tests de integración que no son lentos (test_booking_a_room_charges_the_pro_price, test_room_is_unavailable_after_it_is_booked). pytest tests/integration traería los cinco, no solo los lentos. Para excluir exactamente los lentos necesitas la etiqueta slow y pytest -m "not slow".

La regla que estás descubriendo: la carpeta cubre el corte con el que la ordenaste (la capa); cualquier corte por otra dimensión —criticidad, lentitud, área— que no coincida con la carpeta necesita un marcador. Ese es el segundo eje del módulo.

Ejercicio 2 — Lee la línea de selección. Un compañero corre un comando sobre la suite marcada y te manda la primera línea de la salida: collected 13 items / 3 deselected / 10 selected. Sin ver el comando, responde: (a) ¿Cuántos tests tiene la suite en total? (b) ¿Cuántos van a correr? (c) ¿Los 3 deseleccionados fallaron? (d) ¿Qué comando pudo haber corrido, si sabes que tres tests están marcados slow?

Ver solución
  • (a) Trece. collected 13 items es el total que pytest encontró al recorrer el árbol; la selección viene después de coleccionar. Marcar y seleccionar no cambia cuántos tests existen, solo cuántos se corren.
  • (b) Diez. 10 selected son los que van a ejecutarse. La suma cuadra: 3 deselected + 10 selected = 13 collected.
  • (c) No. Deseleccionar no es fallar. Un test deseleccionado no corre: pytest decidió, por la expresión -m, que no entra en esta corrida. No pasó ni falló; simplemente quedó fuera. "Fallar" es un test que corrió y su aserción no se cumplió, y eso aparece como failed, no como deselected.
  • (d) pytest -m "not slow". Si tres tests están marcados slow y la corrida deseleccionó exactamente tres, la expresión que los excluye es not slow: selecciona todo lo que no tiene la etiqueta slow, o sea 13 - 3 = 10. (También encajaría cualquier expresión que resulte en esos mismos tres deseleccionados, pero not slow es la lectura directa.)

Lo que estás practicando es leer la aritmética de la selección, que es cómo verificas que un -m seleccionó lo que querías: el conteo deseleccionados + seleccionados = coleccionados siempre cuadra, y "deseleccionado" nunca significa "roto".

Ejercicio 3 — Distingue los dos peligros del marcador suelto. Sin registro ni --strict-markers, un marcador es solo una etiqueta libre que pytest acepta sin revisar. Explica, para cada escenario, qué sale mal y por qué el registro lo evitaría. (a) Alguien escribe @pytest.mark.smoek (typo de smoke) en un test que debía ser de humo, y luego corre pytest -m smoke antes de desplegar. (b) Alguien nuevo abre la suite y ve @pytest.mark.wip en varios tests, pero no hay ningún lado que diga qué significa wip ni si se corre o se salta.

Ver solución
  • (a) El test crítico desaparece de la suite de humo, en silencio. Sin --strict-markers, pytest trata smoek como un marcador nuevo y válido —no sabe que querías smoke—, así que el test queda etiquetado smoek, no smoke. Cuando corres pytest -m smoke antes de desplegar, ese test no está en la selección: se deseleccionó porque su etiqueta no coincide. No hay error, solo un warning fácil de perder, y tu chequeo pre-despliegue corre un test menos de los que crees. Si el marcador estuviera registrado y --strict-markers activo, smoek no sería un marcador válido: pytest cortaría con un error de colección ('smoek' not found in markers configuration option) apenas lo viera, y arreglarías el typo antes de que llegara a producción.
  • (b) Nadie sabe qué hace el marcador ni cómo se comporta la suite. wip (de work in progress) podría significar "en progreso, todavía no cuenta" o cualquier otra cosa; sin registro, no hay un lugar autoritativo que lo diga. La persona nueva tiene que adivinar leyendo dónde se usa. El registro cura esto porque markers = ["wip: test en progreso, se excluye del CI"] en la config documenta el marcador en un solo lugar, visible con pytest --markers. El registro no es solo para cazar typos; es la documentación del vocabulario de marcadores del proyecto, el lugar donde un recién llegado ve qué etiquetas existen y qué significa cada una.

Los dos peligros —el typo silencioso y el marcador sin documentar— tienen la misma cura, que es la lección 4: registrar los marcadores en la config y activar la revisión estricta. Registrar los convierte de etiquetas libres y frágiles en un vocabulario cerrado, revisado y documentado.

Resumen y siguiente paso

En esta lección entendiste el cambio de foco del módulo: del eje único de la carpeta (módulo 3) al segundo eje de los marcadores, y a la capa de configuración que los gobierna. Viste la analogía que lo sostiene: los pasillos del supermercado —dónde vive cada producto— más las etiquetas de color que cruzan los pasillos —una propiedad que agrupa sin mover nada—. Y lo viste ejecutado: la suite de Reservo en capas no puede cortar "lo crítico" ni "todo menos lo lento" con una carpeta, pero pytest -m "not slow" lo hace de un tajo —3 deselected / 10 selected, y de 1.24s a 0.01s—, cortando incluso dentro de un archivo, algo que ninguna carpeta puede.

Conociste las dos capas que el módulo construye —los marcadores como eje de categorización, y la configuración central como el contrato que los registra, los vuelve estrictos y fija cómo corre la suite—, el orden de las seis lecciones, y la frontera con los módulos 6 (plugins, que extienden pytest) y 7 (la config por entorno, que varía el comportamiento). Aquí solo usamos lo que pytest trae, y el archivo de config es único.

Antes de avanzar deberías poder: explicar por qué un test tiene un solo eje de carpeta y qué corte queda fuera de alcance por eso; leer la línea N deselected / M selected y saber que deseleccionar no es fallar; y nombrar los dos peligros de un marcador sin registrar —el typo silencioso y el marcador sin documentar—.

Lo que sigue es la definición precisa. En la lección 2 vas a ver, con el mecanismo al descubierto, qué es exactamente un marcador: cómo @pytest.mark.smoke le pega una etiqueta a un test sin tocar lo que verifica, por qué la carpeta no puede capturar esa misma dimensión, y la demostración ejecutada de que un test unitario y uno de integración comparten la etiqueta smoke aunque vivan en carpetas distintas —y -m smoke los junta a los dos—. Es la pieza fundamental sobre la que se apoya todo el resto del módulo.

Recursos