Saltar al contenido
Lakehouse LabLakehouse LabPreparación Databricks Data Engineer
Módulo 28 · Professional

Calidad de código

Contenido abierto

Puedes leer todo sin registrarte. Solo crearemos un perfil anónimo cuando decidas guardar tu progreso.

Professional

Proyectos Python, dependencias y pruebas

Convierte notebooks en un paquete comprobable con límites claros y entornos reproducibles.

Lectura pública
Al terminar podrás
  • Diseñar un paquete Python modular
  • Gestionar wheels y dependencias
  • Probar DataFrames y contratos
Ver fuentes y revisión

Metadatos editoriales

Última revisión
21 jul 2026
Nivel
Professional
Ruta relacionada
delivery
Dominios blueprint
Developing Code · Testing
Estado
Revisión editorial interna
Fuentes principales
Python unit testing in the workspace · What are workspace files?
Reportar un error
01
Modelo mental

Estructura src y separación de I/O

Separa lógica de negocio, adaptadores Databricks y puntos de entrada para que el mismo código pueda probarse sin ejecutar un notebook completo.

Objetivo
Separa lógica de negocio, adaptadores Databricks y puntos de entrada para que el mismo código pueda probarse sin ejecutar un notebook completo.
Duración estimada
17 min aprox.
Dificultad
Professional
Prerrequisitos
m12
Reportar un error en esta lección

Un proyecto mantenible sitúa el paquete importable bajo `src/`, las pruebas bajo `tests/` y deja los notebooks como orquestadores finos. La lógica que transforma DataFrames vive en funciones con entradas y salidas explícitas; acceso a widgets, secretos, paths y escritura se encapsula en adaptadores. Así un cambio de notebook no es la única unidad desplegable ni verificable.

`pyproject.toml` declara el paquete, versión de Python, dependencias y herramientas. El layout `src/` evita importar accidentalmente el código desde el directorio de trabajo en vez del artefacto instalado. En Databricks Runtime 16.0 o superior, un notebook no debe usarse como módulo Python: refactoriza código compartido a archivos `.py` o a una wheel.

Modelo mental

Un notebook es una interfaz de trabajo, no una frontera arquitectónica. La lógica de negocio debería vivir en funciones y módulos puros que reciben DataFrames o valores y devuelven resultados; los adaptadores se ocupan de spark.table, widgets, secrets, escritura y APIs; el punto de entrada conecta ambos. Esta separación crea seams donde los tests sustituyen catálogos y servicios sin simular un workspace entero. También hace explícitas las dependencias y evita estado oculto de celdas ejecutadas fuera de orden. Un proyecto autosuficiente conserva notebooks delgados para exploración u orquestación, pero el comportamiento crítico se importa desde un paquete versionado que puede ejecutarse localmente, en CI y en Jobs con el mismo código.

Función pura de transformación

Función cuya salida depende sólo de entradas explícitas y no realiza lecturas, escrituras ni acceso oculto a configuración externa.

Puede probarse con datos pequeños y reutilizarse en notebook, job o pipeline sin preparar todo el entorno.
Adaptador

Componente que traduce entre la lógica interna y una dependencia concreta como Unity Catalog, REST, secrets o almacenamiento.

Aísla cambios de plataforma y permite sustituir la dependencia en tests unitarios sin falsear reglas de negocio.
Entrypoint

Punto de ejecución que carga configuración, crea dependencias y coordina lectura, transformación, validación y publicación del workload.

Mantiene orchestration visible y evita que módulos importados ejecuten efectos secundarios de forma accidental.
PythonTransformación importable sin estado global
# src/orders/transforms.py
from pyspark.sql import DataFrame, functions as F

def valid_orders(df: DataFrame) -> DataFrame:
    return (
        df.where(F.col("order_id").isNotNull())
          .where(F.col("net_amount") >= 0)
          .withColumn("order_date", F.to_date("ordered_at"))
          .select("order_id", "customer_id", "order_date", "net_amount")
    )

La función no conoce catálogo, widgets ni modo de escritura; esas decisiones pertenecen al entry point del job.

Puntos clave

  • Notebooks coordinan; módulos Python implementan lógica reutilizable.
  • Separa transformaciones puras de lectura, escritura, widgets y secretos.
  • Usa layout `src/` para probar el paquete que realmente distribuyes.

Evita

  • Encapsular toda la pipeline en un notebook con variables globales y orden de celdas implícito.
  • Importar un notebook como módulo cuando el runtime moderno exige archivos Python para código compartido.

Recuerdo activo

¿Por qué una función de transformación no debería leer directamente `dbutils.widgets`?

Borrador privado · solo en este navegador
02
Implementación

Wheels, PyPI y versiones fijadas

Diseña contratos de DataFrame explícitos para que los tests detecten cambios de esquema, nulos, duplicados y semántica, no sólo errores de ejecución.

Objetivo
Diseña contratos de DataFrame explícitos para que los tests detecten cambios de esquema, nulos, duplicados y semántica, no sólo errores de ejecución.
Duración estimada
17 min aprox.
Dificultad
Professional
Prerrequisitos
m12
Reportar un error en esta lección

Spark evalúa de forma perezosa, por lo que construir un DataFrame no demuestra que la transformación sea válida. Las pruebas deben materializar un resultado pequeño y comparar esquema, filas y casos borde. Crea fixtures mínimas que incluyan nulos, duplicados, timestamps y valores inválidos; evita copiar datasets productivos con información sensible.

Una transformación testable recibe DataFrames y parámetros ordinarios. Para comparar resultados, ordena por claves deterministas o utiliza utilidades de aserción de DataFrames disponibles en tu stack; nunca dependas del orden natural de particiones. Separa el contrato técnico —tipos y columnas— del contrato de negocio —por ejemplo, un único pedido por ID—.

Modelo mental

Un contrato de DataFrame describe significado además de columnas. Incluye nombres, tipos, nulabilidad, claves, unicidad, rangos, relaciones y reglas temporales; también aclara qué evolución es compatible. Un schema test detecta que amount cambió de decimal a string, pero no que se duplicó cada order_id o se invirtió el signo de devoluciones. Los tests de transformación deben cubrir ejemplos pequeños que representen equivalencias, nulos, duplicados, datos tardíos y límites. Comparar DataFrames exige normalizar orden o usar comparación insensible cuando el orden no forma parte del contrato. El objetivo no es replicar Spark con mocks, sino ejecutar Spark sobre casos precisos y separar lógica de invariantes de calidad de datos productivos.

Contrato semántico

Especificación de schema, claves, calidad y significado que una transformación promete aceptar y producir para sus consumidores.

Detecta regresiones que compilan y ejecutan correctamente pero alteran significado, unicidad o integridad del producto de datos.
Partición de equivalencia

Clase de entradas que deberían activar el mismo comportamiento, representada por pocos casos cuidadosamente elegidos en las pruebas.

Aporta cobertura significativa sin enumerar combinaciones infinitas ni depender de enormes copias de producción.
Comparación canónica

Normalización de orden, tipos y representación antes de contrastar dos DataFrames que deben ser semánticamente equivalentes.

Evita fallos por orden distribuido no garantizado y mantiene visibles las diferencias que sí pertenecen al contrato.
PythonPrueba unitaria de una transformación Spark
# tests/test_transforms.py
from orders.transforms import valid_orders

def test_valid_orders_rejects_negative_amounts(spark):
    source = spark.createDataFrame(
        [(1, 7, "2026-07-20T10:00:00Z", 12.5),
         (2, 8, "2026-07-20T11:00:00Z", -4.0)],
        "order_id long, customer_id long, ordered_at string, net_amount double",
    )

    actual = valid_orders(source).orderBy("order_id").collect()

    assert [row.order_id for row in actual] == [1]
    assert str(actual[0].order_date) == "2026-07-20"

El fixture prueba un comportamiento concreto; añade tests separados para nulos, zona horaria y esquema.

Puntos clave

  • Materializa acciones pequeñas para ejecutar el plan bajo prueba.
  • Compara esquema y contenido con orden determinista.
  • Incluye casos borde sintéticos y libres de datos personales.

Evita

  • Afirmar sólo que `df.count()` no lanza error sin comprobar valores o esquema.
  • Comparar listas no ordenadas y aceptar tests intermitentes por particionado.

Recuerdo activo

¿Por qué `result = transform(df)` no basta como test?

Borrador privado · solo en este navegador
03
Operación

Funciones transform puras

Distribuye wheels reproducibles y fija dependencias en el nivel correcto para evitar que notebook, job y serverless resuelvan entornos distintos.

Objetivo
Distribuye wheels reproducibles y fija dependencias en el nivel correcto para evitar que notebook, job y serverless resuelvan entornos distintos.
Duración estimada
17 min aprox.
Dificultad
Professional
Prerrequisitos
m12
Reportar un error en esta lección

Una wheel empaqueta módulos y metadatos en un artefacto versionado. Declara dependencias directas en `pyproject.toml`, crea un lock o constraints para CI y fija versiones cuando la reproducibilidad lo exija. En jobs, instala la wheel como librería de la tarea; para compute clásico puedes almacenarla en workspace files o Unity Catalog Volumes según modo de acceso y runtime.

Serverless usa entornos y no admite init scripts. Fija paquetes y prueba el environment version seleccionado. Evita `%pip install` disperso por notebooks productivos: puede reiniciar Python, cambiar precedencia y hacer que dos tareas ejecuten código distinto. La promoción debe mover el mismo hash de wheel, no reconstruirla de fuentes diferentes en cada ambiente.

Modelo mental

Una wheel es una unidad inmutable de distribución Python: contiene código y metadatos de versión, no la promesa de que cualquier entorno resolverá dependencias igual. La reproducibilidad requiere separar dependencias de ejecución, desarrollo y plataforma, fijar rangos o lock donde corresponde y construir una vez para promover el mismo artefacto. Instalar desde una celda hace que cada run resuelva el mundo de nuevo y puede producir resultados distintos entre notebook, job y serverless. Las librerías ya proporcionadas por Databricks, como PySpark, suelen declararse de forma que el desarrollo conozca su API sin empaquetarlas innecesariamente. Las dependencias nativas exigen compatibilidad con arquitectura y runtime, no sólo un nombre en pyproject.

Wheel

Formato de distribución Python construido e instalable que empaqueta código, metadatos y puntos de entrada con una versión definida.

Crea un artefacto promovible y trazable en lugar de copiar código o reinstalarlo de forma ad hoc.
Lock de dependencias

Resolución concreta y versionada de paquetes transitivos usada para reconstruir un entorno equivalente de manera deliberada.

Reduce drift entre CI y ejecución, aunque debe actualizarse con pruebas para recibir correcciones de seguridad.
Build once, promote

Práctica de construir un artefacto una vez y mover exactamente sus mismos bytes por test y producción.

Elimina diferencias introducidas por reconstrucciones y permite atribuir el comportamiento a una versión verificable.
PythonMetadatos mínimos de paquete en pyproject.toml
# pyproject.toml
[build-system]
requires = ["hatchling==1.27.0"]
build-backend = "hatchling.build"

[project]
name = "orders-pipeline"
version = "1.4.0"
requires-python = ">=3.11"
dependencies = [
  "pydantic==2.11.7",
]

[tool.pytest.ini_options]
testpaths = ["tests"]

No declares PySpark a ciegas como dependencia runtime si lo proporciona el entorno Databricks; documenta cómo lo aporta cada target de pruebas.

Puntos clave

  • Promueve el mismo artefacto inmutable entre dev, test y prod.
  • Declara dependencias directas y controla resolución transitiva.
  • Adapta la instalación a serverless, access mode y ubicación gobernada.

Evita

  • Reconstruir la wheel en prod y obtener dependencias transitivas distintas a test.
  • Usar init scripts para gestionar paquetes de serverless, donde no están soportados.

Recuerdo activo

¿Qué riesgo evita promover exactamente la misma wheel de test a prod?

Borrador privado · solo en este navegador
04
Diagnóstico

assertDataFrameEqual y assertSchemaEqual

Combina tests unitarios rápidos con integración Databricks para cubrir catálogos, permisos, formatos y comportamiento distribuido.

Objetivo
Combina tests unitarios rápidos con integración Databricks para cubrir catálogos, permisos, formatos y comportamiento distribuido.
Duración estimada
17 min aprox.
Dificultad
Professional
Prerrequisitos
m12
Reportar un error en esta lección

Los tests unitarios verifican funciones y contratos con datos pequeños; no demuestran que el service principal pueda leer Unity Catalog, que la wheel se instale o que un `MERGE` sea idempotente. Las pruebas de integración despliegan en un catálogo aislado, ejecutan el entry point con identidad real y validan side effects. Deben usar nombres únicos por ejecución y limpiar sólo recursos etiquetados como temporales.

Databricks permite ejecutar pytest en el workspace y Databricks Connect puede acercar desarrollo local al compute remoto. Mantén la pirámide: muchas pruebas sin red, menos integraciones y pocas pruebas end-to-end. Marca suites y establece timeouts para que CI no ejecute por accidente una carga completa.

Modelo mental

Los tests unitarios y de integración responden preguntas distintas. Un unit test pregunta si una regla produce el resultado correcto con entradas controladas y sin depender de recursos externos. Una integración pregunta si el artefacto funciona con Spark, Unity Catalog, Delta, identidades, permisos, red y configuración reales. Simular todo en unit tests no demuestra que un GRANT exista; ejecutar todos los casos en un workspace hace feedback lento y caro. La pirámide adecuada concentra combinaciones y límites en tests rápidos, y reserva pocos recorridos representativos para fronteras de plataforma. Cada test posee datos y recursos aislados, limpia lo que crea y emite evidencia suficiente para distinguir fallo funcional de fallo ambiental.

Test unitario

Prueba rápida y aislada de una regla o componente con entradas controladas y dependencias externas sustituidas por contratos simples.

Permite explorar numerosos casos límite y localizar regresiones sin pagar despliegue ni variabilidad de plataforma.
Test de integración

Prueba que ejecuta el artefacto contra servicios reales relevantes para validar compatibilidad, permisos, formatos y comportamiento distribuido.

Detecta fallos que una simulación local no reproduce, especialmente en Unity Catalog, Delta, red e identidad.
Aislamiento por run

Uso de nombres, datos y recursos exclusivos para cada ejecución automatizada de la suite de integración.

Evita colisiones entre pipelines paralelos y permite limpiar con seguridad sólo los objetos creados por esa prueba.
PythonTest de integración idempotente
import pytest

@pytest.mark.integration
def test_merge_is_idempotent(spark, target_table, sample_orders):
    sample_orders.createOrReplaceTempView("incoming_orders")
    run_merge(spark, "incoming_orders", target_table)
    first = spark.table(target_table).count()

    run_merge(spark, "incoming_orders", target_table)
    second = spark.table(target_table).count()

    assert second == first

El fixture `target_table` debe crear un nombre aislado y eliminar sólo ese objeto al finalizar, incluso si el test falla.

Puntos clave

  • Unit tests cubren lógica; integración cubre plataforma, permisos y side effects.
  • Aísla catálogos/esquemas de prueba por ejecución.
  • Marca suites lentas y limita datos, tiempo y coste.

Evita

  • Ejecutar integraciones contra tablas compartidas y generar colisiones entre ramas.
  • Sustituir toda prueba por mocks y no detectar permisos o DDL incompatibles.

Recuerdo activo

¿Qué fallo sólo detectaría probablemente una prueba de integración?

Borrador privado · solo en este navegador
05
Decisión de diseño

Unit, integration y smoke tests

Convierte calidad en una puerta de promoción: formato, tipos, unit tests, integración, seguridad y contrato del artefacto.

Objetivo
Convierte calidad en una puerta de promoción: formato, tipos, unit tests, integración, seguridad y contrato del artefacto.
Duración estimada
17 min aprox.
Dificultad
Professional
Prerrequisitos
m12
Reportar un error en esta lección

Una pipeline CI debe fallar pronto: lint y type check, tests unitarios, build de wheel, escaneo y validación del bundle. La integración se ejecuta con una identidad no humana y permisos mínimos en un target efímero o compartimentado. Conserva reporte de tests, hash del artefacto y commit para reconstruir la decisión de despliegue.

Los secretos pertenecen al proveedor de identidad o al gestor de secretos del CI; prefiere OAuth workload identity/service principal a PAT de usuario. La promoción a producción requiere que el mismo artefacto superado sea referenciado por la configuración prod. Añade rollback a la versión anterior y una comprobación post-deploy antes de dirigir triggers reales.

Modelo mental

Una puerta de promoción es una política de riesgo automatizada. No intenta demostrar que el software es perfecto; exige evidencia proporcional antes de permitir que el mismo artefacto avance. El orden suele ir de barato a costoso: formato y lint, tipos y seguridad, unit tests, build, contract tests e integración. Fallar pronto ahorra capacidad y ofrece feedback concreto. La puerta también valida el artefacto y la configuración que se desplegarán, no sólo el branch. Cobertura porcentual no sustituye casos relevantes y un scan sin política de severidad genera ruido. Las excepciones son decisiones explícitas, con responsable y caducidad, nunca un clic sin registro para saltar una señal incómoda. En automatización Databricks, el nombre vigente es Declarative Automation Bundles; Asset Bundles es el alias que todavía aparece en el blueprint Professional de 2025.

Gate de promoción

Condición automatizada y auditable que exige evidencia definida antes de mover un artefacto al siguiente ambiente de entrega.

Convierte estándares en comportamiento consistente y evita que presión temporal elimine silenciosamente pruebas críticas.
Evidencia de build

Resultados, hashes, reportes y metadatos que vinculan un commit con el artefacto exacto y las verificaciones ejecutadas.

Permite demostrar qué se probó y evita promover bytes distintos de los que recibieron aprobación.
Break-glass

Ruta excepcional y controlada para omitir temporalmente una barrera bajo autorización, registro y seguimiento obligatorios.

Permite responder a emergencias sin convertir la excepción en un bypass cotidiano invisible y permanente.
YAMLOrden de puertas en CI
quality_gates:
  - name: unit
    command: pytest -m "not integration"
  - name: build
    command: python -m build
  - name: bundle_validate
    command: databricks bundle validate -t test
  - name: integration
    command: pytest -m integration
  - name: artifact_manifest
    command: sha256sum dist/*.whl

Adapta el sintaxis al CI elegido; el principio importante es que prod use el hash que superó estas puertas.

Puntos clave

  • Falla rápido antes de consumir compute remoto.
  • CI usa identidad no humana y mínimo privilegio.
  • Firma la promoción con commit y hash del artefacto.

Evita

  • Usar el PAT personal de un desarrollador y perder el pipeline cuando cambia de equipo.
  • Desplegar desde la rama local después de que CI probó otro commit.

Recuerdo activo

¿Qué dos identificadores permiten demostrar qué código llegó a producción?

Borrador privado · solo en este navegador
5 lecciones pendientes

Vista de lectura · sin ejecución

notebook-best-practices

notebook-best-practices · commit b4f55c1

notebooks/covid_eda_modular.py