{"id":567,"date":"2026-07-22T08:17:35","date_gmt":"2026-07-22T08:17:35","guid":{"rendered":"https:\/\/matforge.org\/?p=567","raw":"https:\/\/matforge.org\/?p=567"},"modified":"2026-07-22T08:17:35","modified_gmt":"2026-07-22T08:17:35","slug":"python-testing-patterns-for-scientific-code-beyond-pytest-basics","status":"publish","type":"post","link":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/","title":{"rendered":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest","raw":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 9<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><h2>Comida clave<\/h2>\n<ul>\n<li>PyTest Basics cubre las pruebas unitarias. Los accesorios, la parametrizaci\u00f3n y <code>pytest.approx<\/code> son su punto de partida, no la l\u00ednea de meta.<\/li>\n<li>Las pruebas basadas en la propiedad con hip\u00f3tesis atrapan los casos extremos que a menudo pasan por alto las pruebas manuales. Es utilizado por los principales proyectos cient\u00edficos de Python como Numpy, JAX y PyTorch.<\/li>\n<li>Las pruebas de convergencia num\u00e9rica requieren estrategias de tolerancia espec\u00edficas del solucionador. Las afirmaciones est\u00e1ndar no capturan el comportamiento de convergencia.<\/li>\n<li>Las pruebas de integraci\u00f3n para los flujos de trabajo de simulaci\u00f3n necesitan patrones de burlas que a\u00edslen las dependencias externas mientras se conservan la l\u00f3gica num\u00e9rica.<\/li>\n<li>Las pruebas de reproducibilidad van m\u00e1s all\u00e1 de las pruebas unitarias. Necesita una gesti\u00f3n determinista de semillas y verificaci\u00f3n de punto flotante multiplataforma.<\/li>\n<\/ul>\n<h2>Qu\u00e9 saber primero<\/h2>\n<p>Si ya conoce los conceptos b\u00e1sicos de las pruebas unitarias para el c\u00f3digo cient\u00edfico, es probable que entienda c\u00f3mo escribir una funci\u00f3n <code>test_<\/code>, cu\u00e1ndo usar accesorios y c\u00f3mo <code>pytest.approx<\/code> maneja las comparaciones de punto flotante. Esa es la l\u00ednea de base.<\/p>\n<p>El c\u00f3digo cient\u00edfico introduce desaf\u00edos que las pruebas unitarias est\u00e1ndar no abordan completamente. Los algoritmos num\u00e9ricos convergen o no logran converger. Las simulaciones dependen de solucionadores externos y de sistemas de E\/S. La aritm\u00e9tica de punto flotante puede comportarse de manera diferente en todas las plataformas.<\/p>\n<p>Los casos de borde que nunca ocurren en datos normales, como divisores cero, matrices singulares y relaciones de aspecto extremas, son a menudo donde se esconden los errores m\u00e1s graves.<\/p>\n<p>Este art\u00edculo cubre los patrones de prueba para el c\u00f3digo cient\u00edfico Python m\u00e1s all\u00e1 de lo b\u00e1sico.<\/p>\n<h2>La pir\u00e1mide de prueba del c\u00f3digo cient\u00edfico<\/h2>\n<p>Cada estrategia de prueba tiene un lugar en la pir\u00e1mide. Para el c\u00f3digo cient\u00edfico, la estructura se ve as\u00ed:<\/p>\n<pre><code>    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n    \u2502    System \/ Simulation Tests \u2502  \u2190 Integration-level workflows\n    \u2502    (3-5 tests)               \u2502\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Integration \/ Workflow      \u2502  \u2190 Multi-step simulations\n    \u2502  Tests (10-20 tests)         \u2502  \u2190 Data pipelines\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Property-Based Tests        \u2502  \u2190 Hypothesis with numerical properties\n    \u2502  (50-100 tests)              \u2502  \u2190 Convergence testing\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Unit Tests                  \u2502  \u2190 Numerical assertions\n    \u2502  (50-200 tests)              \u2502  \u2190 Fixtures for solvers and meshes\n    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n<p>Las pruebas unitarias forman la base. Por encima de ellos se encuentran las pruebas basadas en la propiedad, las pruebas de integraci\u00f3n y las pruebas de simulaci\u00f3n a nivel de sistema. Estas capas superiores hacen que las pruebas sean \u00fatiles para flujos de trabajo cient\u00edficos reales.<\/p>\n<h2>Pruebas basadas en la propiedad con hip\u00f3tesis<\/h2>\n<h3>Por qu\u00e9 las pruebas basadas en la propiedad son importantes para el c\u00f3digo cient\u00edfico<\/h3>\n<p>La hip\u00f3tesis no es s\u00f3lo una herramienta de ense\u00f1anza. Es utilizado por las principales bibliotecas cient\u00edficas de Python para encontrar errores en las rutinas num\u00e9ricas centrales.<\/p>\n<p>Las pruebas basadas en la propiedad cambian el modelo mental de las pruebas. En lugar de escribir pruebas con una entrada espec\u00edfica y una salida esperada, escribe pruebas que verifican las propiedades matem\u00e1ticas. Luego, la hip\u00f3tesis genera muchas entradas autom\u00e1ticamente, incluidos los casos de borde que no puede pensar en probar manualmente.<\/p>\n<h3>Convertir una prueba escrita a mano en una propiedad basada en la propiedad<\/h3>\n<p>Considere esta prueba para un envoltorio de solucionador num\u00e9rico:<\/p>\n<pre><code class=\"language-python\"># Before: handwritten test with single case\ndef test_solver_preserves_mass():\n    \"\"\"Check mass conservation for one mesh size.\"\"\"\n    result = solver_solve(mass_mesh, dt=0.01)\n    mass_before = sum(result[0])\n    mass_after = sum(result[-1])\n    assert mass_after \/ mass_before == pytest.approx(1.0, rel=1e-3)\n<\/code><\/pre>\n<p>Esta prueba verifica un tama\u00f1o de malla y un paso de tiempo. Puede pasar incluso si el solucionador falla para mallas m\u00e1s grandes o diferentes valores de paso de tiempo.<\/p>\n<p>Aqu\u00ed hay una versi\u00f3n de hip\u00f3tesis:<\/p>\n<pre><code class=\"language-python\">from hypothesis import given, strategies as st\nfrom hypothesis import settings, health_check, suppress\n\n@given(\n    st.integers(min_value=5, max_value=200),\n    st.floats(min_value=1e-6, max_value=1.0)\n)\n@settings(max_examples=100, deadline=None)\n@health_check(\"init_consistency_suppressor\")\ndef test_solver_mass_conservation(n_cells, dt):\n    \"\"\"Mass should be preserved across valid mesh sizes and timesteps.\"\"\"\n    mesh = create_mesh(n_cells)\n    result = solver_solve(mesh, dt=dt)\n    \n    mass_before = float(sum(result[0]))\n    mass_after = float(sum(result[-1]))\n    \n    ratio = mass_after \/ mass_before\n    assert abs(ratio - 1.0) &lt; 1e-2\n<\/code><\/pre>\n<p>Esto detecta problemas que una prueba escrita a mano puede pasar por alto:<\/p>\n<ul>\n<li>Casos de esquina en la generaci\u00f3n de mallas, incluidos dominios delgados y relaciones de aspecto extremas.<\/li>\n<li>Sensibilidad de paso de tiempo, especialmente cuando los valores grandes <code>dt<\/code> desestabilizan los esquemas expl\u00edcitos.<\/li>\n<li>Casos de borde flotante, incluidos l\u00edmites de redondeo y rangos num\u00e9ricos inusuales.<\/li>\n<\/ul>\n<h3>La lista de verificaci\u00f3n de la propiedad para funciones cient\u00edficas<\/h3>\n<p>Antes de escribir una prueba de propiedad, identifique qu\u00e9 propiedades debe satisfacer la funci\u00f3n.<\/p>\n<table class=\"custom-table\">\n<tbody>\n<tr>\n<th>Tipo de propiedad<\/th>\n<th>Ejemplo<\/th>\n<th>Por qu\u00e9 importa<\/th>\n<\/tr>\n<tr>\n<td>Conservaci\u00f3n<\/td>\n<td>La masa, la energ\u00eda o el momento se conservan<\/td>\n<td>Previene la deriva num\u00e9rica<\/td>\n<\/tr>\n<tr>\n<td>Simetr\u00eda<\/td>\n<td><code>f(a, b) == f(b, a)<\/code> para operadores sim\u00e9tricos<\/td>\n<td>Captura errores de implementaci\u00f3n asim\u00e9trica<\/td>\n<\/tr>\n<tr>\n<td>Escalada<\/td>\n<td><code>f(k*a, k*b) == k^n * f(a, b)<\/code> para funciones homog\u00e9neas<\/td>\n<td>Pruebas de consistencia dimensional<\/td>\n<\/tr>\n<tr>\n<td>monotonidad<\/td>\n<td>La temperatura disminuye con la distancia en un simple problema de calor<\/td>\n<td>Comprueba la correcci\u00f3n f\u00edsica<\/td>\n<\/tr>\n<tr>\n<td>L\u00edmites<\/td>\n<td>Los valores permanecen dentro de un rango v\u00e1lido<\/td>\n<td>Previene los resultados no f\u00edsicos<\/td>\n<\/tr>\n<tr>\n<td>Comportamiento nulo<\/td>\n<td><code>f(0)<\/code> Devuelve el valor de caso de borde esperado<\/td>\n<td>Protege contra la divisi\u00f3n por casos cero y singulares<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2>Pruebas de convergencia num\u00e9rica<\/h2>\n<h3>Por qu\u00e9 las pruebas de convergencia son diferentes<\/h3>\n<p>Pruebas unitarias Comprobar si el c\u00f3digo produce una respuesta espec\u00edfica. Las pruebas de convergencia comprueban si el c\u00f3digo se acerca a la respuesta correcta a medida que se refinan los par\u00e1metros.<\/p>\n<p>Esto es cr\u00edtico para los solucionadores iterativos, los esquemas de paso de tiempo y los estudios de refinamiento de malla. Las afirmaciones est\u00e1ndar como <code>assert result == expected<\/code> no capturan el comportamiento de convergencia. Necesita probar los patrones de convergencia.<\/p>\n<h3>Pruebas de convergencia del solucionador<\/h3>\n<p>Diferentes suites de solucionador utilizan diferentes criterios de convergencia. El punto clave es que los criterios de convergencia son espec\u00edficos del solucionador, y la tolerancia predeterminada puede no ser apropiada para todos los problemas.<\/p>\n<pre><code class=\"language-python\">import numpy as np\n\ndef test_solver_convergence():\n    \"\"\"Test that the solver converges with mesh refinement.\"\"\"\n    residuals = []\n    for n in [16, 32, 64, 128, 256]:\n        mesh = create_mesh(n)\n        result = solve_with_convergence(\n            mesh,\n            criterion=\"default\",\n            tolerance=1e-8\n        )\n        residuals.append(result.residual)\n    \n    residuals = np.array(residuals)\n    \n    # Test that residuals decrease monotonically with tolerance\n    for i in range(1, len(residuals)):\n        if residuals[i] &gt; residuals[i-1] * 1.01:\n            raise AssertionError(\n                f\"Residual increased at refinement level {i}: \"\n                f\"{residuals[i-1]:.2e} \u2192 {residuals[i]:.2e}\"\n            )\n    \n    # Test convergence rate\n    rate = np.log(residuals[0] \/ residuals[-1]) \/ np.log(16)\n    assert rate &gt;= 1.8, f\"Convergence rate too slow: {rate:.2f}\"\n<\/code><\/pre>\n<h3>estrategias de tolerancia<\/h3>\n<p>Las pruebas deben cubrir los criterios de tolerancia m\u00e1s comunes utilizados por la pila del solucionador.<\/p>\n<pre><code class=\"language-python\">@pytest.mark.parametrize(\"criterion\", [\n    \"default\",\n    \"unscaled\",\n    \"preconditioned\",\n    \"natural\",\n])\ndef test_convergence_criteria(criterion):\n    \"\"\"Verify each criterion produces reasonable residuals.\"\"\"\n    mesh = create_mesh(64)\n    result = solve_with_convergence(mesh, criterion=criterion)\n    \n    assert result.status in (\n        \"convergence\",\n        \"absolute_tolerance_convergence\",\n        \"relative_tolerance_convergence\",\n        \"happy_breakdown\"\n    ) or result.residual &lt; 1e-2\n<\/code><\/pre>\n<h3>Probando casos de divergencia<\/h3>\n<p>Tambi\u00e9n debe probar que el c\u00f3digo maneja la divergencia con gracia.<\/p>\n<pre><code class=\"language-python\">def test_divergence_handling():\n    \"\"\"Code should not crash on divergent cases.\"\"\"\n    mesh = create_mesh(64)\n    result = solve_with_convergence(\n        mesh,\n        criterion=\"default\",\n        divergence_tolerance=100\n    )\n    \n    assert hasattr(result, \"status\")\n    assert hasattr(result, \"residual\")\n<\/code><\/pre>\n<h2>Pruebas de integraci\u00f3n para flujos de trabajo de simulaci\u00f3n<\/h2>\n<h3>Por qu\u00e9 importa las pruebas de integraci\u00f3n<\/h3>\n<p>Las pruebas unitarias verifican las funciones individuales. Las pruebas de integraci\u00f3n verifican que las funciones funcionen juntas correctamente.<\/p>\n<p>Para el c\u00f3digo cient\u00edfico, esto significa probar simulaciones de varios pasos, canalizaciones de datos, patrones de acoplamiento de solucionador y flujos de trabajo de E\/S.<\/p>\n<h3>Patr\u00f3n 1: burlas de las dependencias externas en el c\u00f3digo cient\u00edfico<\/h3>\n<p>Cuando pruebe las funciones que llaman a API externas, bases de datos o sistemas de archivos, utilice una simulaci\u00f3n para aislar la l\u00f3gica num\u00e9rica.<\/p>\n<pre><code class=\"language-python\">from unittest.mock import patch, MagicMock\n\ndef test_simulation_io():\n    \"\"\"Test simulation I\/O without actually writing to disk.\"\"\"\n    \n    with patch(\"h5py.File\") as mock_file:\n        mock_dataset = MagicMock()\n        mock_file.return_value = MagicMock()\n        mock_file.return_value.__enter__ = MagicMock(return_value=mock_file)\n        mock_file.return_value.__exit__ = MagicMock(return_value=None)\n        \n        data = prepare_simulation_output(mesh_size=(100, 100, 100))\n        \n        assert \"simulation_data\" in mock_file.return_value.keys()\n        assert data.shape == (100, 100, 100)\n<\/code><\/pre>\n<p>Esto a\u00edsla la l\u00f3gica de preparaci\u00f3n de datos de la escritura del archivo HDF5. La prueba verifica que su c\u00f3digo formatea y empaqueta los datos correctamente sin esperar en E\/S.<\/p>\n<h3>Patr\u00f3n 2: accesorios parametrizados para barridos de simulaci\u00f3n<\/h3>\n<p>En lugar de probar manualmente algunos tama\u00f1os de malla, use accesorios parametrizados para barridos sistem\u00e1ticos.<\/p>\n<pre><code class=\"language-python\">@pytest.fixture(params=[10, 20, 40, 80, 160])\ndef mesh_sizes(request):\n    \"\"\"Systematic mesh refinement series.\"\"\"\n    return request.param\n\n@pytest.fixture(params=[\n    (1e-3, \"fine\"),\n    (1e-2, \"medium\"), \n    (1e-1, \"coarse\")\n])\ndef timesteps(request):\n    \"\"\"Test different timestep regimes.\"\"\"\n    dt, label = request.param\n    return dt, label\n\ndef test_convergence_with_sweeps(mesh_sizes, timesteps):\n    \"\"\"Test convergence across mesh and timestep refinement.\"\"\"\n    dt, label = timesteps\n    \n    result = solve_convergence_study(\n        mesh_sizes,\n        dt=dt,\n        quantity=\"mass_conservation\"\n    )\n    \n    assert len(result.residuals) == len(result.mesh_sizes)\n    assert all(r &gt; 0 for r in result.residuals)\n<\/code><\/pre>\n<h3>Patr\u00f3n 3: Prueba de flujos de trabajo de varios pasos<\/h3>\n<p>Las simulaciones cient\u00edficas a menudo tienen canalizaciones de varios pasos: generaci\u00f3n de mallas, resoluci\u00f3n, posprocesamiento y producci\u00f3n de escritura.<\/p>\n<pre><code class=\"language-python\">def test_full_workflow():\n    \"\"\"Test the complete simulation pipeline.\"\"\"\n    # Step 1: Generate mesh\n    mesh = generate_mesh(geometry=\"block\", resolution=40)\n    \n    # Step 2: Solve\n    solution = solve(mesh, solver=\"scipy\", tolerance=1e-8)\n    \n    # Step 3: Post-process\n    metrics = compute_metrics(solution, quantities=[\"mass\", \"energy\"])\n    \n    # Step 4: Verify outputs\n    assert metrics[\"mass\"] &gt; 0\n    assert metrics[\"energy\"] &lt; 1e6\n    \n    # Step 5: Write output\n    output_path = write_output(solution, path=\"\/tmp\/test_output.h5\")\n    \n    # Verify output is readable\n    readback = h5py.File(output_path, \"r\")\n    assert \"solution\" in readback.keys()\n<\/code><\/pre>\n<h2>Mofas avanzadas para bibliotecas cient\u00edficas<\/h2>\n<h3>El reto<\/h3>\n<p>Probar el c\u00f3digo cient\u00edfico puede ser lento porque las funciones numpy, scipy y fipy pueden tener tipos de devoluci\u00f3n complejos y rutas de ejecuci\u00f3n costosas. Moverlos le permite probar r\u00e1pidamente la l\u00f3gica de envoltura.<\/p>\n<pre><code class=\"language-python\">import numpy as np\nfrom unittest.mock import patch\n\ndef test_solver_wrapper():\n    \"\"\"Test that your wrapper calls scipy.sparse.linalg correctly.\"\"\"\n    \n    with patch(\"scipy.sparse.linalg.spsolve\") as mock_solve:\n        mock_solve.return_value = np.zeros(100)\n        \n        result = your_solver_wrapper(mesh_size=100)\n        \n        mock_solve.assert_called_once()\n        call_args = mock_solve.call_args\n        assert isinstance(call_args[0], np.ndarray)\n        assert isinstance(call_args[1], np.ndarray)\n<\/code><\/pre>\n<p>Esto verifica la l\u00f3gica de envoltura sin esperar a que se ejecute el solucionador real. La prueba puede terminar en milisegundos en lugar de segundos.<\/p>\n<h3>Prueba de ida y vuelta para datos cient\u00edficos<\/h3>\n<p>La prueba de que los datos cient\u00edficos sobreviven a un ciclo de lectura-escritura y lectura es un patr\u00f3n poderoso.<\/p>\n<pre><code class=\"language-python\">from hypothesis import given, strategies as st\n\n@given(st.integers(min_value=10, max_value=200))\ndef test_hdf5_roundtrip(n_cells):\n    \"\"\"Writing and reading HDF5 data should preserve values.\"\"\"\n    mesh = create_mesh(n_cells)\n    solution = solve(mesh, dt=0.01)\n    \n    path = write_to_hdf5(solution, path=\"\/tmp\/test_roundtrip.h5\")\n    \n    readback = read_from_hdf5(path)\n    \n    assert np.allclose(readback, solution, rtol=1e-6, atol=1e-8)\n<\/code><\/pre>\n<p>Esto detecta errores de serializaci\u00f3n de datos, desajustes de tipo DD y problemas de fragmentaci\u00f3n de HDF5 que podr\u00edan corromper los resultados de forma silenciosa.<\/p>\n<h2>Prueba de reproducibilidad<\/h2>\n<h3>Manejo determinista de semillas<\/h3>\n<p>Las operaciones de punto flotante pueden ser deterministas cuando la semilla es fija, pero la reproducibilidad tambi\u00e9n requiere versiones de biblioteca consistentes, banderas de compilaci\u00f3n y patrones de descomposici\u00f3n de MPI.<\/p>\n<pre><code class=\"language-python\">def test_reproducible_seed():\n    \"\"\"Fixed seed should produce identical results.\"\"\"\n    np.random.seed(42)\n    \n    mesh1 = generate_mesh(seed=42)\n    solution1 = solve(mesh1)\n    \n    np.random.seed(42)\n    mesh2 = generate_mesh(seed=42)\n    solution2 = solve(mesh2)\n    \n    assert np.allclose(solution1, solution2)\n<\/code><\/pre>\n<h3>Pruebas de punto flotante multiplataforma<\/h3>\n<p>Diferentes plataformas pueden producir resultados de punto flotante ligeramente diferentes debido a las diferencias de hardware y biblioteca.<\/p>\n<pre><code class=\"language-python\">def test_floating_point_stability():\n    \"\"\"Results should be consistent across platforms within tolerance.\"\"\"\n    mesh = create_mesh(64)\n    solution = solve(mesh)\n    \n    # All values should be finite\n    assert np.all(np.isfinite(solution))\n    \n    # Values should not be suspiciously small\n    assert np.all(np.abs(solution) &gt; np.finfo(np.float64).tiny * 1e-3)\n<\/code><\/pre>\n<h2>Lo que recomendamos: La estrategia de prueba<\/h2>\n<p>Para un proyecto cient\u00edfico de Python de producci\u00f3n, utilice una estrategia de prueba en capas.<\/p>\n<h3>Debe tener<\/h3>\n<ol>\n<li>Pruebas unitarias con <code>pytest.approx<\/code> para afirmaciones num\u00e9ricas.<\/li>\n<li>Accesorios parametrizados para pruebas sistem\u00e1ticas a trav\u00e9s de par\u00e1metros.<\/li>\n<li>Pruebas de convergencia que verifican el comportamiento del solucionador a trav\u00e9s del refinamiento.<\/li>\n<\/ol>\n<h3>Deber\u00eda tener<\/h3>\n<ol start=\"4\">\n<li>Pruebas basadas en propiedades de hip\u00f3tesis para casos de borde y robustez num\u00e9rica.<\/li>\n<li>Pruebas de integraci\u00f3n para flujos de trabajo de simulaci\u00f3n de varios pasos.<\/li>\n<li>Estrategias de burla para dependencias externas e interfaces de solucionador.<\/li>\n<\/ol>\n<h3>bueno tener<\/h3>\n<ol start=\"7\">\n<li>Pruebas de reproducibilidad multiplataforma.<\/li>\n<li>Pruebas de datos de ida y vuelta que escriben, leen y verifican los datos cient\u00edficos.<\/li>\n<li>Puntos de referencia de rendimiento con <code>pytest-benchmark<\/code>.<\/li>\n<\/ol>\n<h2>Errores comunes<\/h2>\n<h3>Error 1: usar incorrectamente las aserciones dependientes de la tolerancia<\/h3>\n<p>No escriba pruebas num\u00e9ricas como <code>assert result == expected<\/code>. Use <code>pytest.approx<\/code> o afirmaciones con reconocimiento de tolerancia para c\u00f3digo num\u00e9rico. La tolerancia depende de la escala del problema, por lo que una tolerancia fija puede fallar para problemas mayores.<\/p>\n<h3>Error 2: ignorar la convergencia<\/h3>\n<p>Si el c\u00f3digo utiliza solucionadores iterativos, no omita las pruebas de convergencia. Un solucionador que funciona en una malla puede divergir en otro. Comportamiento de convergencia de prueba, no solo el resultado final.<\/p>\n<h3>Error 3: Probando solo el camino feliz<\/h3>\n<p>Las pruebas basadas en la propiedad lo obligan a probar los casos de borde que a menudo pasan por alto las pruebas manuales. Agregue al menos una prueba de hip\u00f3tesis para cada funci\u00f3n num\u00e9rica principal. Puede revelar errores que las pruebas normales basadas en ejemplos nunca detectan.<\/p>\n<h2>Resumen<\/h2>\n<p>Las pruebas unitarias son necesarias, pero no son suficientes para el c\u00f3digo cient\u00edfico. Las pruebas basadas en propiedades, las pruebas de convergencia, los flujos de trabajo de integraci\u00f3n y las comprobaciones de reproducibilidad abordan los desaf\u00edos espec\u00edficos que introducen las simulaciones num\u00e9ricas.<\/p>\n<p>La idea clave es que las pruebas cient\u00edficas no se trata solo de verificar que el c\u00f3digo produce una respuesta correcta. Se trata de verificar que el c\u00f3digo se comporta correctamente. Debe converger, conservar, escalar y manejar los casos de borde con gracia.<\/p>\n<p>Comience con los patrones imprescindibles, luego agregue los patrones deber-tener a medida que madura el conjunto de pruebas. Use los patrones agradables para endurecer la base de c\u00f3digo con el tiempo.<\/p>\n<p>Las pruebas basadas en propiedades con hip\u00f3tesis son una de las adiciones de mayor valor que puede hacer porque detecta errores que a menudo pasan por alto las pruebas manuales.<\/p>\n<h2>Gu\u00edas relacionadas<\/h2>\n<ul>\n<li><a href=\"https:\/\/matforge.org\/unit-testing-scientific-code-pytest-strategies-research-projects\/\">pruebas unitarias para c\u00f3digo cient\u00edfico: estrategias pytest para proyectos de investigaci\u00f3n<\/a> \u2014 La base que necesita antes de esto art\u00edculo.<\/li>\n<li><a href=\"https:\/\/matforge.org\/when-to-use-fem-fvm-fdm\/\">cu\u00e1ndo usar FEM, FVM o FDM: una comparaci\u00f3n pr\u00e1ctica para principiantes<\/a> \u2014 Contexto sobre m\u00e9todos num\u00e9ricos.<\/li>\n<li><a href=\"https:\/\/matforge.org\/mesh-quality-convergence-studies-guide-2\/\">Estudios de calidad y convergencia de malla: una gu\u00eda pr\u00e1ctica para simulaciones cient\u00edficas<\/a> \u2014 Contexto de prueba de convergencia relacionado.<\/li>\n<\/ul>\n<h2>Qu\u00e9 hacer a continuaci\u00f3n<\/h2>\n<p>Si acaba de comenzar a escribir pruebas para c\u00f3digo cient\u00edfico, comience con los patrones imprescindibles anteriores. Una vez que est\u00e1n en su lugar, agregue pruebas de hip\u00f3tesis para las funciones num\u00e9ricas m\u00e1s importantes, especialmente funciones llamadas repetidamente en simulaciones.<\/p>\n<p>Para los equipos que trabajan en software cient\u00edfico de producci\u00f3n, la inversi\u00f3n en pruebas basadas en la propiedad puede dar sus frutos r\u00e1pidamente. Es la misma estrategia utilizada por las principales bibliotecas cient\u00edficas de Python y es una de las formas m\u00e1s efectivas de encontrar errores de casos de borde en c\u00f3digo num\u00e9rico.<\/p>\n<p>Si necesita ayuda para dise\u00f1ar una estrategia de prueba para un proyecto de simulaci\u00f3n, considere reservar una consulta. Una revisi\u00f3n pr\u00e1ctica puede asignar su estructura de c\u00f3digo a pruebas unitarias, pruebas de convergencia, pruebas de integraci\u00f3n y comprobaciones de reproducibilidad.<\/p>\n","protected":false,"raw":"<h2>Comida clave<\/h2>\n<ul>\n<li>PyTest Basics cubre las pruebas unitarias. Los accesorios, la parametrizaci\u00f3n y <code>pytest.approx<\/code> son su punto de partida, no la l\u00ednea de meta.<\/li>\n<li>Las pruebas basadas en la propiedad con hip\u00f3tesis atrapan los casos extremos que a menudo pasan por alto las pruebas manuales. Es utilizado por los principales proyectos cient\u00edficos de Python como Numpy, JAX y PyTorch.<\/li>\n<li>Las pruebas de convergencia num\u00e9rica requieren estrategias de tolerancia espec\u00edficas del solucionador. Las afirmaciones est\u00e1ndar no capturan el comportamiento de convergencia.<\/li>\n<li>Las pruebas de integraci\u00f3n para los flujos de trabajo de simulaci\u00f3n necesitan patrones de burlas que a\u00edslen las dependencias externas mientras se conservan la l\u00f3gica num\u00e9rica.<\/li>\n<li>Las pruebas de reproducibilidad van m\u00e1s all\u00e1 de las pruebas unitarias. Necesita una gesti\u00f3n determinista de semillas y verificaci\u00f3n de punto flotante multiplataforma.<\/li>\n<\/ul>\n<h2>Qu\u00e9 saber primero<\/h2>\n<p>Si ya conoce los conceptos b\u00e1sicos de las pruebas unitarias para el c\u00f3digo cient\u00edfico, es probable que entienda c\u00f3mo escribir una funci\u00f3n <code>test_<\/code>, cu\u00e1ndo usar accesorios y c\u00f3mo <code>pytest.approx<\/code> maneja las comparaciones de punto flotante. Esa es la l\u00ednea de base.<\/p>\n<p>El c\u00f3digo cient\u00edfico introduce desaf\u00edos que las pruebas unitarias est\u00e1ndar no abordan completamente. Los algoritmos num\u00e9ricos convergen o no logran converger. Las simulaciones dependen de solucionadores externos y de sistemas de E\/S. La aritm\u00e9tica de punto flotante puede comportarse de manera diferente en todas las plataformas.<\/p>\n<p>Los casos de borde que nunca ocurren en datos normales, como divisores cero, matrices singulares y relaciones de aspecto extremas, son a menudo donde se esconden los errores m\u00e1s graves.<\/p>\n<p>Este art\u00edculo cubre los patrones de prueba para el c\u00f3digo cient\u00edfico Python m\u00e1s all\u00e1 de lo b\u00e1sico.<\/p>\n<h2>La pir\u00e1mide de prueba del c\u00f3digo cient\u00edfico<\/h2>\n<p>Cada estrategia de prueba tiene un lugar en la pir\u00e1mide. Para el c\u00f3digo cient\u00edfico, la estructura se ve as\u00ed:<\/p>\n<pre><code>    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n    \u2502    System \/ Simulation Tests \u2502  \u2190 Integration-level workflows\n    \u2502    (3-5 tests)               \u2502\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Integration \/ Workflow      \u2502  \u2190 Multi-step simulations\n    \u2502  Tests (10-20 tests)         \u2502  \u2190 Data pipelines\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Property-Based Tests        \u2502  \u2190 Hypothesis with numerical properties\n    \u2502  (50-100 tests)              \u2502  \u2190 Convergence testing\n    \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524\n    \u2502  Unit Tests                  \u2502  \u2190 Numerical assertions\n    \u2502  (50-200 tests)              \u2502  \u2190 Fixtures for solvers and meshes\n    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n<\/code><\/pre>\n<p>Las pruebas unitarias forman la base. Por encima de ellos se encuentran las pruebas basadas en la propiedad, las pruebas de integraci\u00f3n y las pruebas de simulaci\u00f3n a nivel de sistema. Estas capas superiores hacen que las pruebas sean \u00fatiles para flujos de trabajo cient\u00edficos reales.<\/p>\n<h2>Pruebas basadas en la propiedad con hip\u00f3tesis<\/h2>\n<h3>Por qu\u00e9 las pruebas basadas en la propiedad son importantes para el c\u00f3digo cient\u00edfico<\/h3>\n<p>La hip\u00f3tesis no es s\u00f3lo una herramienta de ense\u00f1anza. Es utilizado por las principales bibliotecas cient\u00edficas de Python para encontrar errores en las rutinas num\u00e9ricas centrales.<\/p>\n<p>Las pruebas basadas en la propiedad cambian el modelo mental de las pruebas. En lugar de escribir pruebas con una entrada espec\u00edfica y una salida esperada, escribe pruebas que verifican las propiedades matem\u00e1ticas. Luego, la hip\u00f3tesis genera muchas entradas autom\u00e1ticamente, incluidos los casos de borde que no puede pensar en probar manualmente.<\/p>\n<h3>Convertir una prueba escrita a mano en una propiedad basada en la propiedad<\/h3>\n<p>Considere esta prueba para un envoltorio de solucionador num\u00e9rico:<\/p>\n<pre><code class=\"language-python\"># Before: handwritten test with single case\ndef test_solver_preserves_mass():\n    \"\"\"Check mass conservation for one mesh size.\"\"\"\n    result = solver_solve(mass_mesh, dt=0.01)\n    mass_before = sum(result[0])\n    mass_after = sum(result[-1])\n    assert mass_after \/ mass_before == pytest.approx(1.0, rel=1e-3)\n<\/code><\/pre>\n<p>Esta prueba verifica un tama\u00f1o de malla y un paso de tiempo. Puede pasar incluso si el solucionador falla para mallas m\u00e1s grandes o diferentes valores de paso de tiempo.<\/p>\n<p>Aqu\u00ed hay una versi\u00f3n de hip\u00f3tesis:<\/p>\n<pre><code class=\"language-python\">from hypothesis import given, strategies as st\nfrom hypothesis import settings, health_check, suppress\n\n@given(\n    st.integers(min_value=5, max_value=200),\n    st.floats(min_value=1e-6, max_value=1.0)\n)\n@settings(max_examples=100, deadline=None)\n@health_check(\"init_consistency_suppressor\")\ndef test_solver_mass_conservation(n_cells, dt):\n    \"\"\"Mass should be preserved across valid mesh sizes and timesteps.\"\"\"\n    mesh = create_mesh(n_cells)\n    result = solver_solve(mesh, dt=dt)\n    \n    mass_before = float(sum(result[0]))\n    mass_after = float(sum(result[-1]))\n    \n    ratio = mass_after \/ mass_before\n    assert abs(ratio - 1.0) &lt; 1e-2\n<\/code><\/pre>\n<p>Esto detecta problemas que una prueba escrita a mano puede pasar por alto:<\/p>\n<ul>\n<li>Casos de esquina en la generaci\u00f3n de mallas, incluidos dominios delgados y relaciones de aspecto extremas.<\/li>\n<li>Sensibilidad de paso de tiempo, especialmente cuando los valores grandes <code>dt<\/code> desestabilizan los esquemas expl\u00edcitos.<\/li>\n<li>Casos de borde flotante, incluidos l\u00edmites de redondeo y rangos num\u00e9ricos inusuales.<\/li>\n<\/ul>\n<h3>La lista de verificaci\u00f3n de la propiedad para funciones cient\u00edficas<\/h3>\n<p>Antes de escribir una prueba de propiedad, identifique qu\u00e9 propiedades debe satisfacer la funci\u00f3n.<\/p>\n<table class=\"custom-table\">\n<tbody><tr>\n<th>Tipo de propiedad<\/th>\n<th>Ejemplo<\/th>\n<th>Por qu\u00e9 importa<\/th>\n<\/tr>\n<tr>\n<td>Conservaci\u00f3n<\/td>\n<td>La masa, la energ\u00eda o el momento se conservan<\/td>\n<td>Previene la deriva num\u00e9rica<\/td>\n<\/tr>\n<tr>\n<td>Simetr\u00eda<\/td>\n<td><code>f(a, b) == f(b, a)<\/code> para operadores sim\u00e9tricos<\/td>\n<td>Captura errores de implementaci\u00f3n asim\u00e9trica<\/td>\n<\/tr>\n<tr>\n<td>Escalada<\/td>\n<td><code>f(k*a, k*b) == k^n * f(a, b)<\/code> para funciones homog\u00e9neas<\/td>\n<td>Pruebas de consistencia dimensional<\/td>\n<\/tr>\n<tr>\n<td>monotonidad<\/td>\n<td>La temperatura disminuye con la distancia en un simple problema de calor<\/td>\n<td>Comprueba la correcci\u00f3n f\u00edsica<\/td>\n<\/tr>\n<tr>\n<td>L\u00edmites<\/td>\n<td>Los valores permanecen dentro de un rango v\u00e1lido<\/td>\n<td>Previene los resultados no f\u00edsicos<\/td>\n<\/tr>\n<tr>\n<td>Comportamiento nulo<\/td>\n<td><code>f(0)<\/code> Devuelve el valor de caso de borde esperado<\/td>\n<td>Protege contra la divisi\u00f3n por casos cero y singulares<\/td>\n<\/tr>\n<\/tbody><\/table>\n<h2>Pruebas de convergencia num\u00e9rica<\/h2>\n<h3>Por qu\u00e9 las pruebas de convergencia son diferentes<\/h3>\n<p>Pruebas unitarias Comprobar si el c\u00f3digo produce una respuesta espec\u00edfica. Las pruebas de convergencia comprueban si el c\u00f3digo se acerca a la respuesta correcta a medida que se refinan los par\u00e1metros.<\/p>\n<p>Esto es cr\u00edtico para los solucionadores iterativos, los esquemas de paso de tiempo y los estudios de refinamiento de malla. Las afirmaciones est\u00e1ndar como <code>assert result == expected<\/code> no capturan el comportamiento de convergencia. Necesita probar los patrones de convergencia.<\/p>\n<h3>Pruebas de convergencia del solucionador<\/h3>\n<p>Diferentes suites de solucionador utilizan diferentes criterios de convergencia. El punto clave es que los criterios de convergencia son espec\u00edficos del solucionador, y la tolerancia predeterminada puede no ser apropiada para todos los problemas.<\/p>\n<pre><code class=\"language-python\">import numpy as np\n\ndef test_solver_convergence():\n    \"\"\"Test that the solver converges with mesh refinement.\"\"\"\n    residuals = []\n    for n in [16, 32, 64, 128, 256]:\n        mesh = create_mesh(n)\n        result = solve_with_convergence(\n            mesh,\n            criterion=\"default\",\n            tolerance=1e-8\n        )\n        residuals.append(result.residual)\n    \n    residuals = np.array(residuals)\n    \n    # Test that residuals decrease monotonically with tolerance\n    for i in range(1, len(residuals)):\n        if residuals[i] &gt; residuals[i-1] * 1.01:\n            raise AssertionError(\n                f\"Residual increased at refinement level {i}: \"\n                f\"{residuals[i-1]:.2e} \u2192 {residuals[i]:.2e}\"\n            )\n    \n    # Test convergence rate\n    rate = np.log(residuals[0] \/ residuals[-1]) \/ np.log(16)\n    assert rate &gt;= 1.8, f\"Convergence rate too slow: {rate:.2f}\"\n<\/code><\/pre>\n<h3>estrategias de tolerancia<\/h3>\n<p>Las pruebas deben cubrir los criterios de tolerancia m\u00e1s comunes utilizados por la pila del solucionador.<\/p>\n<pre><code class=\"language-python\">@pytest.mark.parametrize(\"criterion\", [\n    \"default\",\n    \"unscaled\",\n    \"preconditioned\",\n    \"natural\",\n])\ndef test_convergence_criteria(criterion):\n    \"\"\"Verify each criterion produces reasonable residuals.\"\"\"\n    mesh = create_mesh(64)\n    result = solve_with_convergence(mesh, criterion=criterion)\n    \n    assert result.status in (\n        \"convergence\",\n        \"absolute_tolerance_convergence\",\n        \"relative_tolerance_convergence\",\n        \"happy_breakdown\"\n    ) or result.residual &lt; 1e-2\n<\/code><\/pre>\n<h3>Probando casos de divergencia<\/h3>\n<p>Tambi\u00e9n debe probar que el c\u00f3digo maneja la divergencia con gracia.<\/p>\n<pre><code class=\"language-python\">def test_divergence_handling():\n    \"\"\"Code should not crash on divergent cases.\"\"\"\n    mesh = create_mesh(64)\n    result = solve_with_convergence(\n        mesh,\n        criterion=\"default\",\n        divergence_tolerance=100\n    )\n    \n    assert hasattr(result, \"status\")\n    assert hasattr(result, \"residual\")\n<\/code><\/pre>\n<h2>Pruebas de integraci\u00f3n para flujos de trabajo de simulaci\u00f3n<\/h2>\n<h3>Por qu\u00e9 importa las pruebas de integraci\u00f3n<\/h3>\n<p>Las pruebas unitarias verifican las funciones individuales. Las pruebas de integraci\u00f3n verifican que las funciones funcionen juntas correctamente.<\/p>\n<p>Para el c\u00f3digo cient\u00edfico, esto significa probar simulaciones de varios pasos, canalizaciones de datos, patrones de acoplamiento de solucionador y flujos de trabajo de E\/S.<\/p>\n<h3>Patr\u00f3n 1: burlas de las dependencias externas en el c\u00f3digo cient\u00edfico<\/h3>\n<p>Cuando pruebe las funciones que llaman a API externas, bases de datos o sistemas de archivos, utilice una simulaci\u00f3n para aislar la l\u00f3gica num\u00e9rica.<\/p>\n<pre><code class=\"language-python\">from unittest.mock import patch, MagicMock\n\ndef test_simulation_io():\n    \"\"\"Test simulation I\/O without actually writing to disk.\"\"\"\n    \n    with patch(\"h5py.File\") as mock_file:\n        mock_dataset = MagicMock()\n        mock_file.return_value = MagicMock()\n        mock_file.return_value.__enter__ = MagicMock(return_value=mock_file)\n        mock_file.return_value.__exit__ = MagicMock(return_value=None)\n        \n        data = prepare_simulation_output(mesh_size=(100, 100, 100))\n        \n        assert \"simulation_data\" in mock_file.return_value.keys()\n        assert data.shape == (100, 100, 100)\n<\/code><\/pre>\n<p>Esto a\u00edsla la l\u00f3gica de preparaci\u00f3n de datos de la escritura del archivo HDF5. La prueba verifica que su c\u00f3digo formatea y empaqueta los datos correctamente sin esperar en E\/S.<\/p>\n<h3>Patr\u00f3n 2: accesorios parametrizados para barridos de simulaci\u00f3n<\/h3>\n<p>En lugar de probar manualmente algunos tama\u00f1os de malla, use accesorios parametrizados para barridos sistem\u00e1ticos.<\/p>\n<pre><code class=\"language-python\">@pytest.fixture(params=[10, 20, 40, 80, 160])\ndef mesh_sizes(request):\n    \"\"\"Systematic mesh refinement series.\"\"\"\n    return request.param\n\n@pytest.fixture(params=[\n    (1e-3, \"fine\"),\n    (1e-2, \"medium\"), \n    (1e-1, \"coarse\")\n])\ndef timesteps(request):\n    \"\"\"Test different timestep regimes.\"\"\"\n    dt, label = request.param\n    return dt, label\n\ndef test_convergence_with_sweeps(mesh_sizes, timesteps):\n    \"\"\"Test convergence across mesh and timestep refinement.\"\"\"\n    dt, label = timesteps\n    \n    result = solve_convergence_study(\n        mesh_sizes,\n        dt=dt,\n        quantity=\"mass_conservation\"\n    )\n    \n    assert len(result.residuals) == len(result.mesh_sizes)\n    assert all(r &gt; 0 for r in result.residuals)\n<\/code><\/pre>\n<h3>Patr\u00f3n 3: Prueba de flujos de trabajo de varios pasos<\/h3>\n<p>Las simulaciones cient\u00edficas a menudo tienen canalizaciones de varios pasos: generaci\u00f3n de mallas, resoluci\u00f3n, posprocesamiento y producci\u00f3n de escritura.<\/p>\n<pre><code class=\"language-python\">def test_full_workflow():\n    \"\"\"Test the complete simulation pipeline.\"\"\"\n    # Step 1: Generate mesh\n    mesh = generate_mesh(geometry=\"block\", resolution=40)\n    \n    # Step 2: Solve\n    solution = solve(mesh, solver=\"scipy\", tolerance=1e-8)\n    \n    # Step 3: Post-process\n    metrics = compute_metrics(solution, quantities=[\"mass\", \"energy\"])\n    \n    # Step 4: Verify outputs\n    assert metrics[\"mass\"] &gt; 0\n    assert metrics[\"energy\"] &lt; 1e6\n    \n    # Step 5: Write output\n    output_path = write_output(solution, path=\"\/tmp\/test_output.h5\")\n    \n    # Verify output is readable\n    readback = h5py.File(output_path, \"r\")\n    assert \"solution\" in readback.keys()\n<\/code><\/pre>\n<h2>Mofas avanzadas para bibliotecas cient\u00edficas<\/h2>\n<h3>El reto<\/h3>\n<p>Probar el c\u00f3digo cient\u00edfico puede ser lento porque las funciones numpy, scipy y fipy pueden tener tipos de devoluci\u00f3n complejos y rutas de ejecuci\u00f3n costosas. Moverlos le permite probar r\u00e1pidamente la l\u00f3gica de envoltura.<\/p>\n<pre><code class=\"language-python\">import numpy as np\nfrom unittest.mock import patch\n\ndef test_solver_wrapper():\n    \"\"\"Test that your wrapper calls scipy.sparse.linalg correctly.\"\"\"\n    \n    with patch(\"scipy.sparse.linalg.spsolve\") as mock_solve:\n        mock_solve.return_value = np.zeros(100)\n        \n        result = your_solver_wrapper(mesh_size=100)\n        \n        mock_solve.assert_called_once()\n        call_args = mock_solve.call_args\n        assert isinstance(call_args[0], np.ndarray)\n        assert isinstance(call_args[1], np.ndarray)\n<\/code><\/pre>\n<p>Esto verifica la l\u00f3gica de envoltura sin esperar a que se ejecute el solucionador real. La prueba puede terminar en milisegundos en lugar de segundos.<\/p>\n<h3>Prueba de ida y vuelta para datos cient\u00edficos<\/h3>\n<p>La prueba de que los datos cient\u00edficos sobreviven a un ciclo de lectura-escritura y lectura es un patr\u00f3n poderoso.<\/p>\n<pre><code class=\"language-python\">from hypothesis import given, strategies as st\n\n@given(st.integers(min_value=10, max_value=200))\ndef test_hdf5_roundtrip(n_cells):\n    \"\"\"Writing and reading HDF5 data should preserve values.\"\"\"\n    mesh = create_mesh(n_cells)\n    solution = solve(mesh, dt=0.01)\n    \n    path = write_to_hdf5(solution, path=\"\/tmp\/test_roundtrip.h5\")\n    \n    readback = read_from_hdf5(path)\n    \n    assert np.allclose(readback, solution, rtol=1e-6, atol=1e-8)\n<\/code><\/pre>\n<p>Esto detecta errores de serializaci\u00f3n de datos, desajustes de tipo DD y problemas de fragmentaci\u00f3n de HDF5 que podr\u00edan corromper los resultados de forma silenciosa.<\/p>\n<h2>Prueba de reproducibilidad<\/h2>\n<h3>Manejo determinista de semillas<\/h3>\n<p>Las operaciones de punto flotante pueden ser deterministas cuando la semilla es fija, pero la reproducibilidad tambi\u00e9n requiere versiones de biblioteca consistentes, banderas de compilaci\u00f3n y patrones de descomposici\u00f3n de MPI.<\/p>\n<pre><code class=\"language-python\">def test_reproducible_seed():\n    \"\"\"Fixed seed should produce identical results.\"\"\"\n    np.random.seed(42)\n    \n    mesh1 = generate_mesh(seed=42)\n    solution1 = solve(mesh1)\n    \n    np.random.seed(42)\n    mesh2 = generate_mesh(seed=42)\n    solution2 = solve(mesh2)\n    \n    assert np.allclose(solution1, solution2)\n<\/code><\/pre>\n<h3>Pruebas de punto flotante multiplataforma<\/h3>\n<p>Diferentes plataformas pueden producir resultados de punto flotante ligeramente diferentes debido a las diferencias de hardware y biblioteca.<\/p>\n<pre><code class=\"language-python\">def test_floating_point_stability():\n    \"\"\"Results should be consistent across platforms within tolerance.\"\"\"\n    mesh = create_mesh(64)\n    solution = solve(mesh)\n    \n    # All values should be finite\n    assert np.all(np.isfinite(solution))\n    \n    # Values should not be suspiciously small\n    assert np.all(np.abs(solution) &gt; np.finfo(np.float64).tiny * 1e-3)\n<\/code><\/pre>\n<h2>Lo que recomendamos: La estrategia de prueba<\/h2>\n<p>Para un proyecto cient\u00edfico de Python de producci\u00f3n, utilice una estrategia de prueba en capas.<\/p>\n<h3>Debe tener<\/h3>\n<ol>\n<li>Pruebas unitarias con <code>pytest.approx<\/code> para afirmaciones num\u00e9ricas.<\/li>\n<li>Accesorios parametrizados para pruebas sistem\u00e1ticas a trav\u00e9s de par\u00e1metros.<\/li>\n<li>Pruebas de convergencia que verifican el comportamiento del solucionador a trav\u00e9s del refinamiento.<\/li>\n<\/ol>\n<h3>Deber\u00eda tener<\/h3>\n<ol start=\"4\">\n<li>Pruebas basadas en propiedades de hip\u00f3tesis para casos de borde y robustez num\u00e9rica.<\/li>\n<li>Pruebas de integraci\u00f3n para flujos de trabajo de simulaci\u00f3n de varios pasos.<\/li>\n<li>Estrategias de burla para dependencias externas e interfaces de solucionador.<\/li>\n<\/ol>\n<h3>bueno tener<\/h3>\n<ol start=\"7\">\n<li>Pruebas de reproducibilidad multiplataforma.<\/li>\n<li>Pruebas de datos de ida y vuelta que escriben, leen y verifican los datos cient\u00edficos.<\/li>\n<li>Puntos de referencia de rendimiento con <code>pytest-benchmark<\/code>.<\/li>\n<\/ol>\n<h2>Errores comunes<\/h2>\n<h3>Error 1: usar incorrectamente las aserciones dependientes de la tolerancia<\/h3>\n<p>No escriba pruebas num\u00e9ricas como <code>assert result == expected<\/code>. Use <code>pytest.approx<\/code> o afirmaciones con reconocimiento de tolerancia para c\u00f3digo num\u00e9rico. La tolerancia depende de la escala del problema, por lo que una tolerancia fija puede fallar para problemas mayores.<\/p>\n<h3>Error 2: ignorar la convergencia<\/h3>\n<p>Si el c\u00f3digo utiliza solucionadores iterativos, no omita las pruebas de convergencia. Un solucionador que funciona en una malla puede divergir en otro. Comportamiento de convergencia de prueba, no solo el resultado final.<\/p>\n<h3>Error 3: Probando solo el camino feliz<\/h3>\n<p>Las pruebas basadas en la propiedad lo obligan a probar los casos de borde que a menudo pasan por alto las pruebas manuales. Agregue al menos una prueba de hip\u00f3tesis para cada funci\u00f3n num\u00e9rica principal. Puede revelar errores que las pruebas normales basadas en ejemplos nunca detectan.<\/p>\n<h2>Resumen<\/h2>\n<p>Las pruebas unitarias son necesarias, pero no son suficientes para el c\u00f3digo cient\u00edfico. Las pruebas basadas en propiedades, las pruebas de convergencia, los flujos de trabajo de integraci\u00f3n y las comprobaciones de reproducibilidad abordan los desaf\u00edos espec\u00edficos que introducen las simulaciones num\u00e9ricas.<\/p>\n<p>La idea clave es que las pruebas cient\u00edficas no se trata solo de verificar que el c\u00f3digo produce una respuesta correcta. Se trata de verificar que el c\u00f3digo se comporta correctamente. Debe converger, conservar, escalar y manejar los casos de borde con gracia.<\/p>\n<p>Comience con los patrones imprescindibles, luego agregue los patrones deber-tener a medida que madura el conjunto de pruebas. Use los patrones agradables para endurecer la base de c\u00f3digo con el tiempo.<\/p>\n<p>Las pruebas basadas en propiedades con hip\u00f3tesis son una de las adiciones de mayor valor que puede hacer porque detecta errores que a menudo pasan por alto las pruebas manuales.<\/p>\n<h2>Gu\u00edas relacionadas<\/h2>\n<ul>\n<li><a href=\"https:\/\/matforge.org\/unit-testing-scientific-code-pytest-strategies-research-projects\/\">pruebas unitarias para c\u00f3digo cient\u00edfico: estrategias pytest para proyectos de investigaci\u00f3n<\/a> \u2014 La base que necesita antes de esto art\u00edculo.<\/li>\n<li><a href=\"https:\/\/matforge.org\/when-to-use-fem-fvm-fdm\/\">cu\u00e1ndo usar FEM, FVM o FDM: una comparaci\u00f3n pr\u00e1ctica para principiantes<\/a> \u2014 Contexto sobre m\u00e9todos num\u00e9ricos.<\/li>\n<li><a href=\"https:\/\/matforge.org\/mesh-quality-convergence-studies-guide-2\/\">Estudios de calidad y convergencia de malla: una gu\u00eda pr\u00e1ctica para simulaciones cient\u00edficas<\/a> \u2014 Contexto de prueba de convergencia relacionado.<\/li>\n<\/ul>\n<h2>Qu\u00e9 hacer a continuaci\u00f3n<\/h2>\n<p>Si acaba de comenzar a escribir pruebas para c\u00f3digo cient\u00edfico, comience con los patrones imprescindibles anteriores. Una vez que est\u00e1n en su lugar, agregue pruebas de hip\u00f3tesis para las funciones num\u00e9ricas m\u00e1s importantes, especialmente funciones llamadas repetidamente en simulaciones.<\/p>\n<p>Para los equipos que trabajan en software cient\u00edfico de producci\u00f3n, la inversi\u00f3n en pruebas basadas en la propiedad puede dar sus frutos r\u00e1pidamente. Es la misma estrategia utilizada por las principales bibliotecas cient\u00edficas de Python y es una de las formas m\u00e1s efectivas de encontrar errores de casos de borde en c\u00f3digo num\u00e9rico.<\/p>\n<p>Si necesita ayuda para dise\u00f1ar una estrategia de prueba para un proyecto de simulaci\u00f3n, considere reservar una consulta. Una revisi\u00f3n pr\u00e1ctica puede asignar su estructura de c\u00f3digo a pruebas unitarias, pruebas de convergencia, pruebas de integraci\u00f3n y comprobaciones de reproducibilidad.<\/p>\n"},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">Reading Time: <\/span> <span class=\"rt-time\"> 9<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>Comida clave PyTest Basics cubre las pruebas unitarias. Los accesorios, la parametrizaci\u00f3n y pytest.approx son su punto de partida, no la l\u00ednea de meta. Las pruebas basadas en la propiedad con hip\u00f3tesis atrapan los casos extremos que a menudo pasan por alto las pruebas manuales. Es utilizado por los principales proyectos cient\u00edficos de Python como [&hellip;]<\/p>\n","protected":false,"raw":""},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"es_ES","_original_post":"https:\/\/matforge.org\/?p=365","iawp_total_views":0,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-567","post","type-post","status-publish","format-standard","hentry","category-simulation-modeling-projects","es-ES"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.1 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Patrones de prueba de Python para c\u00f3digo cient\u00edfico<\/title>\n<meta name=\"description\" content=\"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/\" \/>\n<meta property=\"og:locale\" content=\"es_ES\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Patrones de prueba de Python para c\u00f3digo cient\u00edfico\" \/>\n<meta property=\"og:description\" content=\"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/\" \/>\n<meta property=\"og:site_name\" content=\"matforge.org\" \/>\n<meta property=\"article:published_time\" content=\"2026-07-22T08:17:35+00:00\" \/>\n<meta name=\"author\" content=\"Elena Markovska\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Escrito por\" \/>\n\t<meta name=\"twitter:data1\" content=\"Elena Markovska\" \/>\n\t<meta name=\"twitter:label2\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data2\" content=\"14 minutos\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/\"},\"author\":{\"name\":\"Elena Markovska\",\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/980162bb5de46742daece973661d93da\"},\"headline\":\"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest\",\"datePublished\":\"2026-07-22T08:17:35+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/\"},\"wordCount\":1975,\"commentCount\":0,\"articleSection\":[\"Simulaci\u00f3n &amp; Proyectos de modelado\"],\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/\",\"url\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/\",\"name\":\"Patrones de prueba de Python para c\u00f3digo cient\u00edfico\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/#website\"},\"datePublished\":\"2026-07-22T08:17:35+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/980162bb5de46742daece973661d93da\"},\"description\":\"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/matforge.org\\\/es\\\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/matforge.org\\\/es\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/matforge.org\\\/#website\",\"url\":\"https:\\\/\\\/matforge.org\\\/\",\"name\":\"matforge.org\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/matforge.org\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/980162bb5de46742daece973661d93da\",\"name\":\"Elena Markovska\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g\",\"caption\":\"Elena Markovska\"},\"sameAs\":[\"http:\\\/\\\/matforge.org\"],\"url\":\"https:\\\/\\\/matforge.org\\\/author\\\/elena-markovska\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico","description":"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/","og_locale":"es_ES","og_type":"article","og_title":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico","og_description":"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.","og_url":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/","og_site_name":"matforge.org","article_published_time":"2026-07-22T08:17:35+00:00","author":"Elena Markovska","twitter_card":"summary_large_image","twitter_misc":{"Escrito por":"Elena Markovska","Tiempo de lectura":"14 minutos"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/#article","isPartOf":{"@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/"},"author":{"name":"Elena Markovska","@id":"https:\/\/matforge.org\/#\/schema\/person\/980162bb5de46742daece973661d93da"},"headline":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest","datePublished":"2026-07-22T08:17:35+00:00","mainEntityOfPage":{"@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/"},"wordCount":1975,"commentCount":0,"articleSection":["Simulaci\u00f3n &amp; Proyectos de modelado"],"inLanguage":"es","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/","url":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/","name":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico","isPartOf":{"@id":"https:\/\/matforge.org\/#website"},"datePublished":"2026-07-22T08:17:35+00:00","author":{"@id":"https:\/\/matforge.org\/#\/schema\/person\/980162bb5de46742daece973661d93da"},"description":"Aprenda patrones avanzados de prueba de Python para c\u00f3digo cient\u00edfico, incluidas hip\u00f3tesis, pruebas de convergencia, pruebas de integraci\u00f3n, burlas y controles de reproducibilidad.","breadcrumb":{"@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/matforge.org\/es\/python-testing-patterns-for-scientific-code-beyond-pytest-basics\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/matforge.org\/es\/"},{"@type":"ListItem","position":2,"name":"Patrones de prueba de Python para c\u00f3digo cient\u00edfico: m\u00e1s all\u00e1 de los conceptos b\u00e1sicos de PyTest"}]},{"@type":"WebSite","@id":"https:\/\/matforge.org\/#website","url":"https:\/\/matforge.org\/","name":"matforge.org","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/matforge.org\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Person","@id":"https:\/\/matforge.org\/#\/schema\/person\/980162bb5de46742daece973661d93da","name":"Elena Markovska","image":{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/secure.gravatar.com\/avatar\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/2de4e35b6581d7d8a839335156c6b5834cfaa0aef537a1c837e882dc57eea1e7?s=96&d=mm&r=g","caption":"Elena Markovska"},"sameAs":["http:\/\/matforge.org"],"url":"https:\/\/matforge.org\/author\/elena-markovska\/"}]}},"_links":{"self":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/567","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/comments?post=567"}],"version-history":[{"count":1,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/567\/revisions"}],"predecessor-version":[{"id":722,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/567\/revisions\/722"}],"wp:attachment":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/media?parent=567"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/categories?post=567"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/tags?post=567"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}