{"id":1238,"date":"2026-08-21T14:28:36","date_gmt":"2026-08-21T14:28:36","guid":{"rendered":"https:\/\/matforge.org\/?p=1238","raw":"https:\/\/matforge.org\/?p=1238"},"modified":"2026-08-21T14:28:36","modified_gmt":"2026-08-21T14:28:36","slug":"unit-testing-scientific-code-pytest-strategies-research-projects","status":"publish","type":"post","link":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/","title":{"rendered":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche","raw":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche"},"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\"> 12<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span><p>Les tests unitaires ne sont pas n\u00e9gociables pour les logiciels scientifiques fiables. Contrairement aux applications commerciales, le code de recherche manque souvent de tests formels, ce qui conduit \u00e0 des r\u00e9sultats irr\u00e9productibles et \u00e0 un effort inutile. Ce guide couvre les strat\u00e9gies PyTest sp\u00e9cifiquement pour les projets Python scientifiques&nbsp;: gestion de la pr\u00e9cision num\u00e9rique avec <code>pytest.approx<\/code>, isolement des d\u00e9pendances externes avec moquerie, utilisation efficace des luminaires et param\u00e9trage, et int\u00e9gration de tests dans des pipelines d&rsquo;int\u00e9gration continue. Vous apprendrez quand utiliser la validation en bo\u00eete noire par rapport aux r\u00e9sultats publi\u00e9s et comment concevoir des tests qui survivent \u00e0 l&rsquo;\u00e9volution du code sans devenir cassants.<\/p>\n<h2>Pourquoi les tests unitaires sont importants dans les logiciels de recherche<\/h2>\n<p>Les logiciels scientifiques existent dans un espace difficile. Il doit \u00eatre suffisamment flexible pour explorer de nouvelles hypoth\u00e8ses, mais suffisamment fiable pour que les r\u00e9sultats publi\u00e9s puissent \u00eatre reproduits des mois ou des ann\u00e9es plus tard. Contrairement aux logiciels commerciaux avec des sp\u00e9cifications claires, le code de recherche \u00e9volue souvent parall\u00e8lement aux exp\u00e9riences, avec des changements d&rsquo;exigences \u00e0 mesure que de nouvelles d\u00e9couvertes \u00e9mergent.<\/p>\n<p>Les cons\u00e9quences d&rsquo;un test inad\u00e9quat dans la recherche sont graves :<\/p>\n<ul>\n<li><strong>R\u00e9sultats irr\u00e9productibles<\/strong>&nbsp;: diff\u00e9rents chercheurs obtiennent des r\u00e9sultats diff\u00e9rents \u00e0 partir du m\u00eame code<\/li>\n<li><strong>Bugs silencieux<\/strong>&nbsp;: erreurs num\u00e9riques qui semblent petites aggrav\u00e9es individuellement en inexactitudes importantes<\/li>\n<li><strong>perte de connaissances<\/strong>&nbsp;: lorsque les d\u00e9veloppeurs originaux partent, les tests servent de documentation ex\u00e9cutable<\/li>\n<li><strong>Efforcement gaspill\u00e9<\/strong>&nbsp;: le d\u00e9bogage devient une chasse aux d\u00e9tectives au lieu d&rsquo;un processus syst\u00e9matique<\/li>\n<\/ul>\n<p>Les tests unitaires r\u00e9solvent ces probl\u00e8mes en validant de petits composants isol\u00e9s de votre code. Chaque test v\u00e9rifie qu&rsquo;une fonction ou une classe sp\u00e9cifique se comporte comme attendue avec des entr\u00e9es d\u00e9finies. Lorsque les tests passent de mani\u00e8re coh\u00e9rente dans tous les environnements, vous disposez de la preuve que votre code produit des r\u00e9sultats fiables.<\/p>\n<p>Mais les tests de code scientifique pr\u00e9sentent des d\u00e9fis uniques auxquels les approches de tests logiciels standard ne r\u00e9pondent pas enti\u00e8rement.<\/p>\n<h2>D\u00e9fis uniques de tester le code scientifique<\/h2>\n<p>Le code scientifique et num\u00e9rique diff\u00e8re des applications commerciales typiques de plusieurs mani\u00e8res qui affectent la strat\u00e9gie de test.<\/p>\n<h3>Pr\u00e9cision num\u00e9rique et erreurs \u00e0 virgule flottante<\/h3>\n<p>L&rsquo;arithm\u00e9tique \u00e0 virgule flottante est intrins\u00e8quement impr\u00e9cise. En raison de la fa\u00e7on dont les ordinateurs repr\u00e9sentent des nombres d\u00e9cimaux, <code>0.1 + 0.2<\/code> n&rsquo;est pas exactement <code>0.3<\/code> dans la repr\u00e9sentation binaire \u00e0 virgule flottante. Dans des simulations scientifiques impliquant des milliers ou des millions d&rsquo;op\u00e9rations, ces petites erreurs s&rsquo;accumulent.<\/p>\n<p>Un test na\u00eff qui utilise une \u00e9galit\u00e9 exacte (<code>==<\/code>) \u00e9chouera par intermittence ou sur un autre mat\u00e9riel&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_numerical_computation():\n    result = complex_simulation()  # returns 1.0000000000000002\n    assert result == 1.0  # FAILS! Even though the difference is negligible\n<\/code><\/pre>\n<p>La solution consiste \u00e0 utiliser des comparaisons bas\u00e9es sur la tol\u00e9rance. PyTest fournit <code>pytest.approx()<\/code> \u00e0 cette fin&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_numerical_computation():\n    result = complex_simulation()\n    assert result == pytest.approx(1.0, rel=1e-9, abs=1e-12)\n<\/code><\/pre>\n<p><code>rel<\/code> (tol\u00e9rance relative) est utile pour des valeurs de toute grandeur&nbsp;; <code>abs<\/code> (Tol\u00e9rance absolue) traite les cas o\u00f9 la valeur attendue est proche de z\u00e9ro. Les valeurs par d\u00e9faut sont <code>rel=1e-6<\/code> et <code>abs=1e-12<\/code>, mais le code scientifique n\u00e9cessite souvent des tol\u00e9rances plus strictes.<\/p>\n<h3>R\u00e9ponses \u00ab\u00a0correctes\u00a0\u00bb inconnues<\/h3>\n<p>Dans de nombreux sc\u00e9narios de recherche, vous n&rsquo;avez pas une sortie correcte connue. La simulation pourrait explorer un territoire inconnu. Comment testez-vous le code lorsque vous ne savez pas quelle devrait \u00eatre la r\u00e9ponse&nbsp;?<\/p>\n<p>Plusieurs strat\u00e9gies fonctionnent :<\/p>\n<ol>\n<li><strong>Op\u00e9rations inverses<\/strong>&nbsp;: si votre code calcule <code>B = f(A)<\/code>, testez \u00e9galement cela <code>A \u2248 f\u207b\u00b9(B)<\/code>.<\/li>\n<li><strong>Lois sur la conservation<\/strong>&nbsp;: pour les simulations physiques, v\u00e9rifiez que la masse, l&rsquo;\u00e9nergie ou la quantit\u00e9 de mouvement est conserv\u00e9e dans le cadre de la tol\u00e9rance.<\/li>\n<li><strong>Cas limitants<\/strong>&nbsp;: test du comportement dans des limites simplifi\u00e9es l\u00e0 o\u00f9 des solutions analytiques existent.<\/li>\n<li><strong>Test de r\u00e9gression<\/strong>&nbsp;: stockez les sorties d&rsquo;une ex\u00e9cution approuv\u00e9e et d\u00e9tectez les modifications inattendues.<\/li>\n<\/ol>\n<h3>D\u00e9pendances externes et calcul lourd<\/h3>\n<p>Le code scientifique d\u00e9pend souvent de :<\/p>\n<ul>\n<li>Grands ensembles de donn\u00e9es (t\u00e9raoctets d&rsquo;entr\u00e9e de simulation)<\/li>\n<li>Solveurs ou biblioth\u00e8ques externes (packages HPC, biblioth\u00e8ques Fortran)<\/li>\n<li>E\/S de fichiers avec des formats complexes<\/li>\n<li>Connexions ou API de base de donn\u00e9es<\/li>\n<\/ul>\n<p>L&rsquo;ex\u00e9cution du syst\u00e8me complet dans chaque test unitaire n&rsquo;est pas pratique. Vous avez besoin d&rsquo;isolement.<\/p>\n<h2>Fonctionnalit\u00e9s PyTest qui r\u00e9solvent les probl\u00e8mes de tests de recherche<\/h2>\n<p>PyTest propose plusieurs fonctionnalit\u00e9s particuli\u00e8rement pr\u00e9cieuses pour le code scientifique.<\/p>\n<h3>Appareils pour la configuration et le d\u00e9montage<\/h3>\n<p>Les luminaires encapsulent le code de configuration qui s&rsquo;ex\u00e9cute avant les tests. Pour les tests scientifiques, les accessoires peuvent :<\/p>\n<ul>\n<li>Cr\u00e9er des donn\u00e9es de test temporaires ou des maillages<\/li>\n<li>Initialiser des objets de simulation avec des param\u00e8tres connus<\/li>\n<li>Nettoyer les fichiers temporaires apr\u00e8s les tests<\/li>\n<li>Fournir des configurations de test r\u00e9utilisables<\/li>\n<\/ul>\n<pre><code class=\"language-python\">import pytest\nimport tempfile\nimport numpy as np\n\n@pytest.fixture\ndef simple_mesh():\n    \"\"\"Create a small 1D mesh for testing.\"\"\"\n    from fipy import Grid1D\n    return Grid1D(dx=0.1, nx=10)\n\n@pytest.fixture\ndef diffusion_solver(simple_mesh):\n    \"\"\"Set up a diffusion solver on the test mesh.\"\"\"\n    from fipy import CellVariable, DiffusionTerm\n    var = CellVariable(name=\"concentration\", mesh=simple_mesh, value=1.0)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    return var, eq\n<\/code><\/pre>\n<h3>Param\u00e9trage de plusieurs sc\u00e9narios<\/h3>\n<p>Au lieu d&rsquo;\u00e9crire des fonctions de test distinctes pour des cas similaires, utilisez <code>@pytest.mark.parametrize<\/code> pour ex\u00e9cuter la m\u00eame logique de test avec des entr\u00e9es diff\u00e9rentes.<\/p>\n<pre><code class=\"language-python\">@pytest.mark.parametrize(\"dx,nx,expected_volume\", [\n    (0.1, 10, 1.0),\n    (0.01, 100, 1.0),\n    (0.001, 1000, 1.0),\n])\ndef test_mesh_volume(dx, nx, expected_volume):\n    \"\"\"Test that mesh volume matches domain size.\"\"\"\n    mesh = Grid1D(dx=dx, nx=nx)\n    assert mesh.cellVolumes.sum() == pytest.approx(expected_volume)\n<\/code><\/pre>\n<p>La param\u00e9trisation est particuli\u00e8rement utile pour :<\/p>\n<ul>\n<li>Test des cas de bord (valeurs nulles, tr\u00e8s petits\/grands nombres)<\/li>\n<li>V\u00e9rification du comportement sur diff\u00e9rentes r\u00e9solutions de maillage<\/li>\n<li>Validation de plusieurs types de conditions aux limites<\/li>\n<li>V\u00e9rification de diverses valeurs de propri\u00e9t\u00e9s de mat\u00e9riaux<\/li>\n<\/ul>\n<p>Pour le code de recherche, vous pouvez param\u00e9trer les r\u00e9sultats de r\u00e9f\u00e9rence connus des articles publi\u00e9s.<\/p>\n<h3>Moquement des d\u00e9pendances externes<\/h3>\n<p>La moquerie remplace les vraies d\u00e9pendances par des contrefa\u00e7ons contr\u00f4l\u00e9es. Cela isole l&rsquo;unit\u00e9 test\u00e9e et rend les tests plus rapides et plus fiables.<\/p>\n<p><strong>Quand se moquer du code scientifique&nbsp;:<\/strong><\/p>\n<ul>\n<li>Fichiers de donn\u00e9es externes&nbsp;: remplacez les grands ensembles de donn\u00e9es par des donn\u00e9es synth\u00e9tiques minimales qui exercent les m\u00eames chemins de code<\/li>\n<li>Solveurs HPC&nbsp;: simulez les biblioth\u00e8ques Fortran co\u00fbteuses avec des impl\u00e9mentations Pure Python qui renvoient des r\u00e9sultats connus<\/li>\n<li>API r\u00e9seau&nbsp;: STUB Remote Services qui fournissent des param\u00e8tres ou une configuration<\/li>\n<li>G\u00e9n\u00e9rateurs de nombres al\u00e9atoires&nbsp;: semez-les pour produire des s\u00e9quences d\u00e9terministes<\/li>\n<\/ul>\n<pre><code class=\"language-python\">from unittest.mock import patch, MagicMock\n\ndef test_simulation_with_external_data():\n    # Mock the data loading function to return small, known data\n    with patch('mycode.load_large_dataset') as mock_load:\n        mock_load.return_value = np.array([1.0, 2.0, 3.0])\n        result = run_simulation()\n        assert result.converged\n<\/code><\/pre>\n<p><strong>Des directives importantes<\/strong>&nbsp;: simulez les d\u00e9pendances de votre propre code, et non les biblioth\u00e8ques tierces que vous ne contr\u00f4lez pas. Suivez le principe \u00ab\u00a0Ne vous moquez pas de ce que vous ne poss\u00e9dez pas\u00a0\u00bb.<\/p>\n<h3>Utilisation de <code>pytest.approx<\/code> pour les comparaisons num\u00e9riques<\/h3>\n<p>Les comparaisons \u00e0 virgule flottante doivent tenir compte des erreurs d&rsquo;arrondi. L&rsquo;objet <code>approx<\/code> de PyTest g\u00e8re cela avec \u00e9l\u00e9gance&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_diffusion_solution():\n    \"\"\"Test that diffusion reaches expected steady state.\"\"\"\n    concentration = solve_diffusion(time=100.0)\n    expected = 0.5  # analytical steady state for this boundary condition\n    assert concentration.mean() == pytest.approx(expected, rel=1e-6)\n<\/code><\/pre>\n<p>Vous pouvez \u00e9galement utiliser <code>approx<\/code> avec les tableaux&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_array_computation():\n    result = compute_field()\n    expected = np.array([1.0, 2.0, 3.0])\n    assert result == pytest.approx(expected)\n<\/code><\/pre>\n<p>Pour le code scientifique, choisissez les tol\u00e9rances bas\u00e9es sur :<\/p>\n<ul>\n<li>La pr\u00e9cision num\u00e9rique de vos m\u00e9thodes (par exemple, la diff\u00e9rence finie du second ordre a une erreur de troncature O(dx\u00b2))<\/li>\n<li>La pr\u00e9cision requise par votre application (Tol\u00e9rance de l&rsquo;ing\u00e9nierie par rapport \u00e0 la recherche exploratoire)<\/li>\n<\/ul>\n<h2>D\u00e9veloppement ax\u00e9 sur les tests pour des projets de recherche<\/h2>\n<p>Le d\u00e9veloppement pilot\u00e9 par les tests (TDD) suit un cycle simple&nbsp;: r\u00e9diger un test d&rsquo;\u00e9chec, puis \u00e9crire un code minimal pour le faire passer, puis refactoriser. Alors que TDD est bien \u00e9tabli dans les logiciels commerciaux, les projets de recherche y r\u00e9sistent souvent en raison des contraintes de temps per\u00e7ues.<\/p>\n<p>La r\u00e9alit\u00e9 : TDD fait gagner du temps dans la recherche en attrapant des bogues avant de se propager \u00e0 travers des exp\u00e9riences. Les tests d&rsquo;\u00e9criture vous obligent d&rsquo;abord \u00e0 clarifier l&rsquo;interface et le comportement attendu de chaque fonction avant la mise en \u0153uvre.<\/p>\n<p><strong>TDD adapt\u00e9 \u00e0 l&rsquo;exploration scientifique&nbsp;:<\/strong><\/p>\n<ol>\n<li>Commencez par un mod\u00e8le ou un algorithme simple que vous comprenez analytiquement<\/li>\n<li>R\u00e9diger des tests qui valident par rapport aux r\u00e9sultats connus (solutions analytiques, cas limitant)<\/li>\n<li>Impl\u00e9menter le code pour r\u00e9ussir ces tests<\/li>\n<li>\u00c9tendez le mod\u00e8le de mani\u00e8re incr\u00e9mentielle, en ajoutant des tests pour chaque nouvelle capacit\u00e9<\/li>\n<li>Lorsque vous d\u00e9couvrez un bogue, \u00e9crivez un test qui le reproduit en premier, puis corrigez-le<\/li>\n<\/ol>\n<p>TDD fonctionne bien pour&nbsp;:<\/p>\n<ul>\n<li>Fonctions utilitaires (g\u00e9n\u00e9ration de maillage, transformations de coordonn\u00e9es)<\/li>\n<li>Op\u00e9rations math\u00e9matiques (manipulations matricielles, fonctions sp\u00e9ciales)<\/li>\n<li>Pipelines de traitement de donn\u00e9es (analyse, filtrage, normalisation)<\/li>\n<li>Validation de la configuration<\/li>\n<\/ul>\n<p>TDD est moins adapt\u00e9 pour :<\/p>\n<ul>\n<li>Code hautement exploratoire o\u00f9 l&rsquo;interface elle-m\u00eame est incertaine<\/li>\n<li>Des scripts uniques qui ne seront pas r\u00e9utilis\u00e9s<\/li>\n<li>Code qui d\u00e9pend de ressources externes non encore disponibles<\/li>\n<\/ul>\n<p>En pratique, une approche hybride fonctionne le mieux : r\u00e9diger des tests pour des composants stables et fondamentaux ; Utilisez des tests d&rsquo;int\u00e9gration plus l\u00e9gers pour les sections exp\u00e9rimentales.<\/p>\n<h2>Organisation de tests pour des projets scientifiques<\/h2>\n<p>O\u00f9 tester les fichiers ? PyTest offre une flexibilit\u00e9 :<\/p>\n<pre><code>my_research_project\/\n\u251c\u2500\u2500 src\/\n\u2502   \u2514\u2500\u2500 mypackage\/\n\u2502       \u251c\u2500\u2500 __init__.py\n\u2502       \u251c\u2500\u2500 solver.py\n\u2502       \u2514\u2500\u2500 mesh.py\n\u251c\u2500\u2500 tests\/\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u251c\u2500\u2500 test_solver.py\n\u2502   \u251c\u2500\u2500 test_mesh.py\n\u2502   \u2514\u2500\u2500 conftest.py  # shared fixtures\n\u251c\u2500\u2500 data\/\n\u2502   \u2514\u2500\u2500 reference_results\/  # stored outputs for regression tests\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u2502       \u2514\u2500\u2500 ci.yml  # GitHub Actions CI configuration\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 README.md\n<\/code><\/pre>\n<p><strong>Conventions cl\u00e9s&nbsp;:<\/strong><\/p>\n<ul>\n<li>Conserver les tests dans un r\u00e9pertoire s\u00e9par\u00e9 <code>tests\/<\/code> parall\u00e8le \u00e0 <code>src\/<\/code> (ou <code>lib\/<\/code>)<\/li>\n<li>Nommer les fichiers de test <code>test_*.py<\/code> ou <code>*_test.py<\/code><\/li>\n<li>Nommer les fonctions de test <code>test_*()<\/code> pour autoriser la d\u00e9couverte automatique de PyTest<\/li>\n<li>Utilisez <code>conftest.py<\/code> pour les luminaires partag\u00e9s entre plusieurs fichiers de test<\/li>\n<\/ul>\n<p>Pour les projets bas\u00e9s sur Fipy, structurez les tests pour correspondre \u00e0 la hi\u00e9rarchie des modules&nbsp;:<\/p>\n<pre><code>fipy_project\/\n\u251c\u2500\u2500 fipy\/\n\u2502   \u251c\u2500\u2500 meshes\/\n\u2502   \u2502   \u2514\u2500\u2500 grid1d.py\n\u2502   \u2514\u2500\u2500 terms\/\n\u2502       \u2514\u2500\u2500 diffusion.py\n\u251c\u2500\u2500 tests\/\n\u2502   \u251c\u2500\u2500 meshes\/\n\u2502   \u2502   \u2514\u2500\u2500 test_grid1d.py\n\u2502   \u2514\u2500\u2500 terms\/\n\u2502       \u2514\u2500\u2500 test_diffusion.py\n<\/code><\/pre>\n<h2>Int\u00e9gration avec int\u00e9gration continue<\/h2>\n<p>Les tests unitaires ne fournissent de la valeur que s&rsquo;ils fonctionnent de mani\u00e8re coh\u00e9rente. Int\u00e9gration continue (CI) Automatise l&rsquo;ex\u00e9cution des tests chaque fois que le code change.<\/p>\n<p>GitHub Actions fournit une configuration CI simple pour les projets Python&nbsp;:<\/p>\n<pre><code class=\"language-yaml\"># .github\/workflows\/ci.yml\nname: CI\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\"]\n\n    steps:\n    - uses: actions\/checkout@v3\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions\/setup-python@v4\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install -e .[test]\n    - name: Run tests with pytest\n      run: |\n        pytest --cov=src --cov-report=xml --cov-report=html\n    - name: Upload coverage\n      uses: codecov\/codecov-action@v3\n<\/code><\/pre>\n<p>CI assure :<\/p>\n<ul>\n<li>Les tests passent sur plusieurs plates-formes (Linux, macOS, Windows)<\/li>\n<li>Les tests r\u00e9ussissent sur plusieurs versions de Python<\/li>\n<li>La couverture du code est suivie dans le temps<\/li>\n<li>Les demandes d&rsquo;extraction sont valid\u00e9es avant la fusion<\/li>\n<\/ul>\n<p>Pour les logiciels de recherche, pensez \u00e0 ajouter :<\/p>\n<ul>\n<li>Tests qui s&rsquo;ex\u00e9cutent avec diff\u00e9rentes versions de d\u00e9pendances cl\u00e9s (numpy, scipy, fipy)<\/li>\n<li>V\u00e9rifications de la r\u00e9gression des performances (les algorithmes ne ralentissent pas)<\/li>\n<li>La documentation construite pour v\u00e9rifier les exemples fonctionne toujours<\/li>\n<\/ul>\n<h2>erreurs courantes et comment les \u00e9viter<\/h2>\n<p>Sur la base de la recherche et des meilleures pratiques de l&rsquo;industrie, voici les erreurs de test unitaire les plus courantes dans le code scientifique&nbsp;:<\/p>\n<h3>1. Tester les d\u00e9tails de l&rsquo;impl\u00e9mentation au lieu du comportement<\/h3>\n<p>Test de la mise en \u0153uvre interne rend les tests cassants. Lorsque vous refactorisez le code, les tests doivent toujours passer si le comportement externe est correct.<\/p>\n<pre><code class=\"language-python\"># \u274c Bad: tests internal state\ndef test_algorithm_updates_counter():\n    obj = MyAlgorithm()\n    obj.step()\n    assert obj.counter == 1  # fragile if counter implementation changes\n\n# \u2705 Better: tests observable outcome\ndef test_algorithm_produces_correct_result():\n    obj = MyAlgorithm()\n    result = obj.run()\n    assert result == expected\n<\/code><\/pre>\n<h3>2. Ignorer la tol\u00e9rance num\u00e9rique<\/h3>\n<p>Les comparaisons exactes sur les r\u00e9sultats \u00e0 virgule flottante provoquent des tests flous qui \u00e9chouent au hasard ou sur un mat\u00e9riel diff\u00e9rent. Utilisez toujours <code>pytest.approx()<\/code> ou des assertions similaires bas\u00e9es sur la tol\u00e9rance pour les sorties num\u00e9riques.<\/p>\n<h3>3. \u00c9crire des tests lents<\/h3>\n<p>Les tests unitaires doivent fonctionner rapidement (millisecondes, pas secondes). Si un test est lent :<\/p>\n<ul>\n<li>Il ne sera pas ex\u00e9cut\u00e9 assez fr\u00e9quemment<\/li>\n<li>Les d\u00e9veloppeurs ignoreront l&rsquo;ex\u00e9cution de la suite de tests compl\u00e8te<\/li>\n<li>CI devient cher et lent<\/li>\n<\/ul>\n<p><strong>Solutions&nbsp;:<\/strong><\/p>\n<ul>\n<li>Utilisez de petits ensembles de donn\u00e9es synth\u00e9tiques au lieu de grands r\u00e9els<\/li>\n<li>simuler des calculs externes co\u00fbteux<\/li>\n<li>S\u00e9parez les tests d&rsquo;int\u00e9gration lente des tests unitaires rapides<\/li>\n<li>Utilisez la param\u00e9trisation \u00e0 bon escient&nbsp;: n&rsquo;ex\u00e9cutez pas de milliers de variations dans chaque test r\u00e9ussi<\/li>\n<\/ul>\n<h3>4. Ne pas isoler les tests<\/h3>\n<p>Les tests ne doivent pas d\u00e9pendre les uns des autres ou de l&rsquo;\u00e9tat global. Chaque test doit :<\/p>\n<ul>\n<li>Cr\u00e9ez ses propres donn\u00e9es de test (utilisez des appareils)<\/li>\n<li>Nettoyer apr\u00e8s lui-m\u00eame<\/li>\n<li>ne pas compter sur l&rsquo;ordre d&rsquo;ex\u00e9cution<\/li>\n<\/ul>\n<pre><code class=\"language-python\"># \u274c Bad: shared mutable state\nresults = []\n\ndef test_first():\n    results.append(1)\n\ndef test_second():\n    assert results == [1]  # fails if tests run in wrong order\n\n# \u2705 Better: independent tests\ndef test_first():\n    result = compute_something()\n    assert result == 1\n\ndef test_second():\n    result = compute_something_else()\n    assert result == 2\n<\/code><\/pre>\n<h3>5. Sauter les tests sans raison valable<\/h3>\n<p><code>@pytest.mark.skip<\/code> doit \u00eatre utilis\u00e9 avec parcimonie. Si un test est ignor\u00e9 parce que l&rsquo;environnement manque de quelque chose, utilisez <code>pytest.importorskip()<\/code> au niveau du module ou rendez la d\u00e9pendance facultative dans la configuration CI.<\/p>\n<h3>6. \u00c9crire de vagues assertions<\/h3>\n<p>Les tests doivent exprimer clairement ce qui est v\u00e9rifi\u00e9 et pourquoi.<\/p>\n<pre><code class=\"language-python\"># \u274c Unclear: what's being tested?\nassert result != None\n\n# \u2705 Clear: specific expectation with context\nassert result.converged is True, \"Solver should converge for well-posed problem\"\n<\/code><\/pre>\n<h3>7. Chemins de codage en dur et hypoth\u00e8ses d&rsquo;environnement<\/h3>\n<p>Utilisez des r\u00e9pertoires et des luminaires temporaires plut\u00f4t que des chemins fixes. Le luminaire <code>tmp_path<\/code> de PyTest fournit un nouveau r\u00e9pertoire temporaire pour chaque test.<\/p>\n<h2>Exemple pratique : test d&rsquo;un solveur de diffusion Fipy<\/h2>\n<p>Rassemblons ces strat\u00e9gies avec un exemple concret pertinent pour le public de Matforge.<\/p>\n<pre><code class=\"language-python\"># tests\/test_diffusion.py\nimport pytest\nimport numpy as np\nfrom fipy import Grid1D, CellVariable, DiffusionTerm\n\n@pytest.fixture\ndef simple_1d_grid():\n    \"\"\"Create a uniform 1D grid for diffusion testing.\"\"\"\n    return Grid1D(dx=0.1, nx=50)\n\n@pytest.fixture\ndef steady_state_diffusion(simple_1d_grid):\n    \"\"\"Set up diffusion with Dirichlet boundaries at both ends.\"\"\"\n    mesh = simple_1d_grid\n    var = CellVariable(name=\"concentration\", mesh=mesh, value=0.0)\n    var.constrain(1.0, mesh.facesLeft)\n    var.constrain(0.0, mesh.facesRight)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    return var, eq\n\ndef test_mesh_volume(simple_1d_grid):\n    \"\"\"Total domain length should equal nx * dx.\"\"\"\n    expected_length = 50 * 0.1\n    assert simple_1d_grid.cellVolumes.sum() == pytest.approx(expected_length)\n\ndef test_diffusion_conservation(steady_state_diffusion):\n    \"\"\"For steady diffusion with no sources, total mass should be conserved.\"\"\"\n    var, eq = steady_state_diffusion\n    eq.solve(var, dt=1.0)\n    # With fixed values at boundaries, mass enters from left and exits right\n    # In steady state, flux in should equal flux out\n    left_flux = var.faceValue[simple_1d_grid.facesLeft.value]\n    right_flux = var.faceValue[simple_1d_grid.facesRight.value]\n    # Flux direction: positive means flow to the right\n    assert left_flux &gt; 0  # mass enters from left\n    assert right_flux &lt; 0  # mass exits from right (negative direction)\n    assert abs(left_flux + right_flux) == pytest.approx(0, abs=1e-10)\n\ndef test_diffusion_solution_shape(steady_state_diffusion):\n    \"\"\"Concentration should decrease monotonically from left to right.\"\"\"\n    var, eq = steady_state_diffusion\n    eq.solve(var, dt=1.0)\n    # Steady state should be linear for constant diffusivity\n    x = simple_1d_grid.cellCenters[0]\n    expected = 1.0 - x \/ (50 * 0.1)  # linear from 1 to 0\n    assert var.value == pytest.approx(expected, rel=1e-5)\n\n@pytest.mark.parametrize(\"dx,nx\", [(0.1, 50), (0.05, 100), (0.02, 250)])\ndef test_mesh_independence(dx, nx):\n    \"\"\"Solution should converge as mesh refines.\"\"\"\n    mesh = Grid1D(dx=dx, nx=nx)\n    var = CellVariable(name=\"c\", mesh=mesh, value=0.0)\n    var.constrain(1.0, mesh.facesLeft)\n    var.constrain(0.0, mesh.facesRight)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    eq.solve(var, dt=1.0)\n    # Check at midpoint\n    mid_idx = nx \/\/ 2\n    assert var.value[mid_idx] == pytest.approx(0.5, rel=0.1)\n<\/code><\/pre>\n<p>Cet exemple illustre :<\/p>\n<ul>\n<li>Appareils pour une configuration de test r\u00e9utilisable<\/li>\n<li>Param\u00e9trage pour tester plusieurs r\u00e9solutions<\/li>\n<li>Affirmations num\u00e9riques bas\u00e9es sur la tol\u00e9rance<\/li>\n<li>Tester les principes physiques (conservation, lin\u00e9arit\u00e9)<\/li>\n<li>Noms et assertions de test clairs et descriptifs<\/li>\n<\/ul>\n<h2>Lorsque les tests unitaires ne suffisent pas<\/h2>\n<p>Les tests unitaires valident des composants individuels, mais les logiciels de recherche ont \u00e9galement besoin :<\/p>\n<ul>\n<li><strong>Tests d&rsquo;int\u00e9gration<\/strong>&nbsp;: v\u00e9rifiez que plusieurs modules fonctionnent correctement ensemble<\/li>\n<li><strong>Tests de syst\u00e8me<\/strong>&nbsp;: ex\u00e9cuter des simulations compl\u00e8tes de bout en bout et comparer aux sorties connues<\/li>\n<li><strong>Tests de performance<\/strong>&nbsp;: s&rsquo;assurer que les algorithmes r\u00e9pondent aux attentes de complexit\u00e9 de calcul<\/li>\n<li><strong>V\u00e9rifications de visualisation<\/strong>&nbsp;: rep\u00e9rer les erreurs de rendu \u00e9videntes (comparaison d&rsquo;images automatis\u00e9e si possible)<\/li>\n<\/ul>\n<p>Une strat\u00e9gie de test compl\u00e8te pour les projets de recherche comprend plusieurs niveaux de test, les tests unitaires formant la base.<\/p>\n<h2>Ce que nous recommandons : une strat\u00e9gie de test pragmatique pour des projets de recherche<\/h2>\n<p>Sur la base des preuves tir\u00e9es des meilleures pratiques de logiciels scientifiques, voici notre approche recommand\u00e9e&nbsp;:<\/p>\n<h3>Commencez par des tests unitaires fondamentaux<\/h3>\n<p>Commencez par \u00e9crire des tests pour&nbsp;:<\/p>\n<ul>\n<li>Fonctions math\u00e9matiques de base (fonctions sp\u00e9ciales, transformations de coordonn\u00e9es)<\/li>\n<li>Utilitaires de g\u00e9n\u00e9ration et de manipulation de maillage<\/li>\n<li>Impl\u00e9mentations des conditions aux limites<\/li>\n<li>Routines d&rsquo;entr\u00e9e\/sortie de donn\u00e9es (validation, mise en forme)<\/li>\n<\/ul>\n<p>Ces composants sont stables, ont des comportements clairs attendus et sont r\u00e9utilis\u00e9s dans de nombreuses simulations.<\/p>\n<h3>Adoptez pytest.approx en standard<\/h3>\n<p>N&rsquo;utilisez jamais <code>==<\/code> pour les r\u00e9sultats \u00e0 virgule flottante. Utilisez toujours <code>pytest.approx()<\/code> avec les tol\u00e9rances appropri\u00e9es. Faites-en une convention d&rsquo;\u00e9quipe.<\/p>\n<h3>Utiliser largement les luminaires<\/h3>\n<p>Les luminaires r\u00e9duisent la duplication et rendent les tests plus maintenables. Cr\u00e9er des luminaires pour&nbsp;:<\/p>\n<ul>\n<li>Maillages communs (grille de test 1D, 2D, 3D)<\/li>\n<li>Configuration des conditions aux limites standard<\/li>\n<li>Solutions d&rsquo;analyse connues<\/li>\n<li>Gestion des fichiers\/r\u00e9pertoires temporaires<\/li>\n<\/ul>\n<h3>Int\u00e9grer CI t\u00f4t<\/h3>\n<p>Configurez des actions GitHub (ou similaires) avant que le projet ne devienne grand. Ex\u00e9cutez automatiquement des tests sur&nbsp;:<\/p>\n<ul>\n<li>Chaque pouss\u00e9e<\/li>\n<li>Chaque demande d&rsquo;extraction<\/li>\n<li>Constructions nocturnes pr\u00e9vues (pour attraper la d\u00e9rive environnementale)<\/li>\n<\/ul>\n<h3>Mesurer et suivre la couverture du code<\/h3>\n<p>Utilisez <code>pytest-cov<\/code> pour mesurer quelles parties de votre code sont exerc\u00e9es par des tests. Visez au moins 80&nbsp;% de couverture sur les modules de base, mais ne vous obs\u00e9dez pas \u00e0 plus de 100&nbsp;%&nbsp;: l&rsquo;objectif est la confiance, pas un score parfait.<\/p>\n<h3>R\u00e9digez des tests lorsque vous corrigez des bogues<\/h3>\n<p>Chaque fois qu&rsquo;un bogue est signal\u00e9, \u00e9crivez un test qui le reproduit avant de corriger. Cela garantit que le bogue ne r\u00e9appara\u00eetra pas plus tard.<\/p>\n<h3>Gardez les tests rapides<\/h3>\n<p>Si un test dure plus de quelques secondes, pensez \u00e0 :<\/p>\n<ul>\n<li>Utilisation de petits probl\u00e8mes de test<\/li>\n<li>Se moquer des op\u00e9rations co\u00fbteuses<\/li>\n<li>D\u00e9placement vers une suite de tests d&rsquo;int\u00e9gration qui s&rsquo;ex\u00e9cute moins fr\u00e9quemment<\/li>\n<\/ul>\n<h2>Guides connexes<\/h2>\n<ul>\n<li><a href=\"\/tracking-long-term-technical-debt-in-research-software\/\">Suivi de la dette technique \u00e0 long terme dans les logiciels de recherche<\/a> \u2013 G\u00e9rer la dette de test \u00e0 mesure que les projets \u00e9voluent<\/li>\n<li><a href=\"\/managing-research-software-through-tickets\/\">Gestion des logiciels de recherche via des tickets<\/a> \u2013 Utilisation du suivi des probl\u00e8mes pour coordonner les efforts de test<\/li>\n<li><a href=\"\/reproducibility-and-its-role-in-debugging\/\">Reproductibilit\u00e9 et son r\u00f4le dans le d\u00e9bogage<\/a> \u2013 Comment les tests permettent le d\u00e9bogage syst\u00e9matique<\/li>\n<li><a href=\"\/how-to-write-a-clear-and-useful-bug-report\/\">comment r\u00e9diger un rapport de bogue clair et utile<\/a> \u2013 Fournir les informations n\u00e9cessaires pour cr\u00e9er des tests de r\u00e9gression<\/li>\n<li><a href=\"\/feature-requests-vs-bug-reports-knowing-the-difference\/\">demandes de fonctionnalit\u00e9s vs. Rapports de bogues&nbsp;: conna\u00eetre la diff\u00e9rence<\/a> \u2013 Classification des probl\u00e8mes qui entra\u00eenent le d\u00e9veloppement des tests<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>Les tests unitaires transforment les logiciels de recherche \u00e0 partir de scripts fragiles en instruments fiables et reproductibles. Bien que la mise en place de tests complets n\u00e9cessite un investissement initial, le gain s&rsquo;explique par une r\u00e9duction du temps de d\u00e9bogage, une confiance accrue dans les r\u00e9sultats et une collaboration plus fluide.<\/p>\n<p>Le framework PyTest fournit des outils puissants &#8211; am\u00e9nagements, param\u00e9trage, moquerie et <code>approx()<\/code> &#8211; qui r\u00e9pondent directement aux d\u00e9fis du code scientifique&nbsp;: pr\u00e9cision num\u00e9rique, d\u00e9pendances externes et r\u00e9ponses correctes inconnues. Associ\u00e9es \u00e0 une int\u00e9gration continue, ces pratiques garantissent que les tests s&rsquo;ex\u00e9cutent de mani\u00e8re coh\u00e9rente dans tous les environnements.<\/p>\n<p>N&rsquo;oubliez pas que les tests ne consistent pas \u00e0 atteindre la perfection. Il s&rsquo;agit de renforcer suffisamment la confiance dans votre code pour que vous puissiez faire confiance \u00e0 ses r\u00e9sultats lorsque cela compte le plus. Commencez par les composants de base, r\u00e9digez des tests qui expriment des attentes claires et \u00e9largissent progressivement la couverture au fur et \u00e0 mesure que le projet se d\u00e9veloppe.<\/p>\n<p>Votre futur moi et toute personne qui h\u00e9rite de votre code vous remerciera.<\/p>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Pr\u00eat \u00e0 ajouter des tests \u00e0 votre projet de recherche&nbsp;?<\/p>\n<ol>\n<li>Installer PyTest&nbsp;: <code>pip install pytest<\/code><\/li>\n<li>Cr\u00e9er un r\u00e9pertoire <code>tests\/<\/code> avec un simple fichier de test<\/li>\n<li>R\u00e9digez un test pour une fonction de base en utilisant <code>pytest.approx<\/code><\/li>\n<li>Configurer un workflow d&rsquo;actions GitHub pour ex\u00e9cuter des tests automatiquement<\/li>\n<li>\u00c9largissez progressivement la couverture lorsque vous modifiez le code<\/li>\n<\/ol>\n<p>Pour une aide personnalis\u00e9e \u00e0 la mise en \u0153uvre de strat\u00e9gies de test dans votre logiciel de recherche sp\u00e9cifique, <a href=\"\/\">nous contacter pour une consultation<\/a> (visitez notre page d&rsquo;accueil pour plus d&rsquo;informations).<\/p>\n","protected":false,"raw":"<p>Les tests unitaires ne sont pas n\u00e9gociables pour les logiciels scientifiques fiables. Contrairement aux applications commerciales, le code de recherche manque souvent de tests formels, ce qui conduit \u00e0 des r\u00e9sultats irr\u00e9productibles et \u00e0 un effort inutile. Ce guide couvre les strat\u00e9gies PyTest sp\u00e9cifiquement pour les projets Python scientifiques&nbsp;: gestion de la pr\u00e9cision num\u00e9rique avec <code>pytest.approx<\/code>, isolement des d\u00e9pendances externes avec moquerie, utilisation efficace des luminaires et param\u00e9trage, et int\u00e9gration de tests dans des pipelines d'int\u00e9gration continue. Vous apprendrez quand utiliser la validation en bo\u00eete noire par rapport aux r\u00e9sultats publi\u00e9s et comment concevoir des tests qui survivent \u00e0 l'\u00e9volution du code sans devenir cassants.<\/p>\n<h2>Pourquoi les tests unitaires sont importants dans les logiciels de recherche<\/h2>\n<p>Les logiciels scientifiques existent dans un espace difficile. Il doit \u00eatre suffisamment flexible pour explorer de nouvelles hypoth\u00e8ses, mais suffisamment fiable pour que les r\u00e9sultats publi\u00e9s puissent \u00eatre reproduits des mois ou des ann\u00e9es plus tard. Contrairement aux logiciels commerciaux avec des sp\u00e9cifications claires, le code de recherche \u00e9volue souvent parall\u00e8lement aux exp\u00e9riences, avec des changements d'exigences \u00e0 mesure que de nouvelles d\u00e9couvertes \u00e9mergent.<\/p>\n<p>Les cons\u00e9quences d'un test inad\u00e9quat dans la recherche sont graves :<\/p>\n<ul>\n<li><strong>R\u00e9sultats irr\u00e9productibles<\/strong>&nbsp;: diff\u00e9rents chercheurs obtiennent des r\u00e9sultats diff\u00e9rents \u00e0 partir du m\u00eame code<\/li>\n<li><strong>Bugs silencieux<\/strong>&nbsp;: erreurs num\u00e9riques qui semblent petites aggrav\u00e9es individuellement en inexactitudes importantes<\/li>\n<li><strong>perte de connaissances<\/strong>&nbsp;: lorsque les d\u00e9veloppeurs originaux partent, les tests servent de documentation ex\u00e9cutable<\/li>\n<li><strong>Efforcement gaspill\u00e9<\/strong>&nbsp;: le d\u00e9bogage devient une chasse aux d\u00e9tectives au lieu d'un processus syst\u00e9matique<\/li>\n<\/ul>\n<p>Les tests unitaires r\u00e9solvent ces probl\u00e8mes en validant de petits composants isol\u00e9s de votre code. Chaque test v\u00e9rifie qu'une fonction ou une classe sp\u00e9cifique se comporte comme attendue avec des entr\u00e9es d\u00e9finies. Lorsque les tests passent de mani\u00e8re coh\u00e9rente dans tous les environnements, vous disposez de la preuve que votre code produit des r\u00e9sultats fiables.<\/p>\n<p>Mais les tests de code scientifique pr\u00e9sentent des d\u00e9fis uniques auxquels les approches de tests logiciels standard ne r\u00e9pondent pas enti\u00e8rement.<\/p>\n<h2>D\u00e9fis uniques de tester le code scientifique<\/h2>\n<p>Le code scientifique et num\u00e9rique diff\u00e8re des applications commerciales typiques de plusieurs mani\u00e8res qui affectent la strat\u00e9gie de test.<\/p>\n<h3>Pr\u00e9cision num\u00e9rique et erreurs \u00e0 virgule flottante<\/h3>\n<p>L'arithm\u00e9tique \u00e0 virgule flottante est intrins\u00e8quement impr\u00e9cise. En raison de la fa\u00e7on dont les ordinateurs repr\u00e9sentent des nombres d\u00e9cimaux, <code>0.1 + 0.2<\/code> n'est pas exactement <code>0.3<\/code> dans la repr\u00e9sentation binaire \u00e0 virgule flottante. Dans des simulations scientifiques impliquant des milliers ou des millions d'op\u00e9rations, ces petites erreurs s'accumulent.<\/p>\n<p>Un test na\u00eff qui utilise une \u00e9galit\u00e9 exacte (<code>==<\/code>) \u00e9chouera par intermittence ou sur un autre mat\u00e9riel&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_numerical_computation():\n    result = complex_simulation()  # returns 1.0000000000000002\n    assert result == 1.0  # FAILS! Even though the difference is negligible\n<\/code><\/pre>\n<p>La solution consiste \u00e0 utiliser des comparaisons bas\u00e9es sur la tol\u00e9rance. PyTest fournit <code>pytest.approx()<\/code> \u00e0 cette fin&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_numerical_computation():\n    result = complex_simulation()\n    assert result == pytest.approx(1.0, rel=1e-9, abs=1e-12)\n<\/code><\/pre>\n<p><code>rel<\/code> (tol\u00e9rance relative) est utile pour des valeurs de toute grandeur&nbsp;; <code>abs<\/code> (Tol\u00e9rance absolue) traite les cas o\u00f9 la valeur attendue est proche de z\u00e9ro. Les valeurs par d\u00e9faut sont <code>rel=1e-6<\/code> et <code>abs=1e-12<\/code>, mais le code scientifique n\u00e9cessite souvent des tol\u00e9rances plus strictes.<\/p>\n<h3>R\u00e9ponses \"correctes\" inconnues<\/h3>\n<p>Dans de nombreux sc\u00e9narios de recherche, vous n'avez pas une sortie correcte connue. La simulation pourrait explorer un territoire inconnu. Comment testez-vous le code lorsque vous ne savez pas quelle devrait \u00eatre la r\u00e9ponse&nbsp;?<\/p>\n<p>Plusieurs strat\u00e9gies fonctionnent :<\/p>\n<ol>\n<li><strong>Op\u00e9rations inverses<\/strong>&nbsp;: si votre code calcule <code>B = f(A)<\/code>, testez \u00e9galement cela <code>A \u2248 f\u207b\u00b9(B)<\/code>.<\/li>\n<li><strong>Lois sur la conservation<\/strong>&nbsp;: pour les simulations physiques, v\u00e9rifiez que la masse, l'\u00e9nergie ou la quantit\u00e9 de mouvement est conserv\u00e9e dans le cadre de la tol\u00e9rance.<\/li>\n<li><strong>Cas limitants<\/strong>&nbsp;: test du comportement dans des limites simplifi\u00e9es l\u00e0 o\u00f9 des solutions analytiques existent.<\/li>\n<li><strong>Test de r\u00e9gression<\/strong>&nbsp;: stockez les sorties d'une ex\u00e9cution approuv\u00e9e et d\u00e9tectez les modifications inattendues.<\/li>\n<\/ol>\n<h3>D\u00e9pendances externes et calcul lourd<\/h3>\n<p>Le code scientifique d\u00e9pend souvent de :<\/p>\n<ul>\n<li>Grands ensembles de donn\u00e9es (t\u00e9raoctets d'entr\u00e9e de simulation)<\/li>\n<li>Solveurs ou biblioth\u00e8ques externes (packages HPC, biblioth\u00e8ques Fortran)<\/li>\n<li>E\/S de fichiers avec des formats complexes<\/li>\n<li>Connexions ou API de base de donn\u00e9es<\/li>\n<\/ul>\n<p>L'ex\u00e9cution du syst\u00e8me complet dans chaque test unitaire n'est pas pratique. Vous avez besoin d'isolement.<\/p>\n<h2>Fonctionnalit\u00e9s PyTest qui r\u00e9solvent les probl\u00e8mes de tests de recherche<\/h2>\n<p>PyTest propose plusieurs fonctionnalit\u00e9s particuli\u00e8rement pr\u00e9cieuses pour le code scientifique.<\/p>\n<h3>Appareils pour la configuration et le d\u00e9montage<\/h3>\n<p>Les luminaires encapsulent le code de configuration qui s'ex\u00e9cute avant les tests. Pour les tests scientifiques, les accessoires peuvent :<\/p>\n<ul>\n<li>Cr\u00e9er des donn\u00e9es de test temporaires ou des maillages<\/li>\n<li>Initialiser des objets de simulation avec des param\u00e8tres connus<\/li>\n<li>Nettoyer les fichiers temporaires apr\u00e8s les tests<\/li>\n<li>Fournir des configurations de test r\u00e9utilisables<\/li>\n<\/ul>\n<pre><code class=\"language-python\">import pytest\nimport tempfile\nimport numpy as np\n\n@pytest.fixture\ndef simple_mesh():\n    \"\"\"Create a small 1D mesh for testing.\"\"\"\n    from fipy import Grid1D\n    return Grid1D(dx=0.1, nx=10)\n\n@pytest.fixture\ndef diffusion_solver(simple_mesh):\n    \"\"\"Set up a diffusion solver on the test mesh.\"\"\"\n    from fipy import CellVariable, DiffusionTerm\n    var = CellVariable(name=\"concentration\", mesh=simple_mesh, value=1.0)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    return var, eq\n<\/code><\/pre>\n<h3>Param\u00e9trage de plusieurs sc\u00e9narios<\/h3>\n<p>Au lieu d'\u00e9crire des fonctions de test distinctes pour des cas similaires, utilisez <code>@pytest.mark.parametrize<\/code> pour ex\u00e9cuter la m\u00eame logique de test avec des entr\u00e9es diff\u00e9rentes.<\/p>\n<pre><code class=\"language-python\">@pytest.mark.parametrize(\"dx,nx,expected_volume\", [\n    (0.1, 10, 1.0),\n    (0.01, 100, 1.0),\n    (0.001, 1000, 1.0),\n])\ndef test_mesh_volume(dx, nx, expected_volume):\n    \"\"\"Test that mesh volume matches domain size.\"\"\"\n    mesh = Grid1D(dx=dx, nx=nx)\n    assert mesh.cellVolumes.sum() == pytest.approx(expected_volume)\n<\/code><\/pre>\n<p>La param\u00e9trisation est particuli\u00e8rement utile pour :<\/p>\n<ul>\n<li>Test des cas de bord (valeurs nulles, tr\u00e8s petits\/grands nombres)<\/li>\n<li>V\u00e9rification du comportement sur diff\u00e9rentes r\u00e9solutions de maillage<\/li>\n<li>Validation de plusieurs types de conditions aux limites<\/li>\n<li>V\u00e9rification de diverses valeurs de propri\u00e9t\u00e9s de mat\u00e9riaux<\/li>\n<\/ul>\n<p>Pour le code de recherche, vous pouvez param\u00e9trer les r\u00e9sultats de r\u00e9f\u00e9rence connus des articles publi\u00e9s.<\/p>\n<h3>Moquement des d\u00e9pendances externes<\/h3>\n<p>La moquerie remplace les vraies d\u00e9pendances par des contrefa\u00e7ons contr\u00f4l\u00e9es. Cela isole l'unit\u00e9 test\u00e9e et rend les tests plus rapides et plus fiables.<\/p>\n<p><strong>Quand se moquer du code scientifique&nbsp;:<\/strong><\/p>\n<ul>\n<li>Fichiers de donn\u00e9es externes&nbsp;: remplacez les grands ensembles de donn\u00e9es par des donn\u00e9es synth\u00e9tiques minimales qui exercent les m\u00eames chemins de code<\/li>\n<li>Solveurs HPC&nbsp;: simulez les biblioth\u00e8ques Fortran co\u00fbteuses avec des impl\u00e9mentations Pure Python qui renvoient des r\u00e9sultats connus<\/li>\n<li>API r\u00e9seau&nbsp;: STUB Remote Services qui fournissent des param\u00e8tres ou une configuration<\/li>\n<li>G\u00e9n\u00e9rateurs de nombres al\u00e9atoires&nbsp;: semez-les pour produire des s\u00e9quences d\u00e9terministes<\/li>\n<\/ul>\n<pre><code class=\"language-python\">from unittest.mock import patch, MagicMock\n\ndef test_simulation_with_external_data():\n    # Mock the data loading function to return small, known data\n    with patch('mycode.load_large_dataset') as mock_load:\n        mock_load.return_value = np.array([1.0, 2.0, 3.0])\n        result = run_simulation()\n        assert result.converged\n<\/code><\/pre>\n<p><strong>Des directives importantes<\/strong>&nbsp;: simulez les d\u00e9pendances de votre propre code, et non les biblioth\u00e8ques tierces que vous ne contr\u00f4lez pas. Suivez le principe \"Ne vous moquez pas de ce que vous ne poss\u00e9dez pas\".<\/p>\n<h3>Utilisation de <code>pytest.approx<\/code> pour les comparaisons num\u00e9riques<\/h3>\n<p>Les comparaisons \u00e0 virgule flottante doivent tenir compte des erreurs d'arrondi. L'objet <code>approx<\/code> de PyTest g\u00e8re cela avec \u00e9l\u00e9gance&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_diffusion_solution():\n    \"\"\"Test that diffusion reaches expected steady state.\"\"\"\n    concentration = solve_diffusion(time=100.0)\n    expected = 0.5  # analytical steady state for this boundary condition\n    assert concentration.mean() == pytest.approx(expected, rel=1e-6)\n<\/code><\/pre>\n<p>Vous pouvez \u00e9galement utiliser <code>approx<\/code> avec les tableaux&nbsp;:<\/p>\n<pre><code class=\"language-python\">def test_array_computation():\n    result = compute_field()\n    expected = np.array([1.0, 2.0, 3.0])\n    assert result == pytest.approx(expected)\n<\/code><\/pre>\n<p>Pour le code scientifique, choisissez les tol\u00e9rances bas\u00e9es sur :<\/p>\n<ul>\n<li>La pr\u00e9cision num\u00e9rique de vos m\u00e9thodes (par exemple, la diff\u00e9rence finie du second ordre a une erreur de troncature O(dx\u00b2))<\/li>\n<li>La pr\u00e9cision requise par votre application (Tol\u00e9rance de l'ing\u00e9nierie par rapport \u00e0 la recherche exploratoire)<\/li>\n<\/ul>\n<h2>D\u00e9veloppement ax\u00e9 sur les tests pour des projets de recherche<\/h2>\n<p>Le d\u00e9veloppement pilot\u00e9 par les tests (TDD) suit un cycle simple&nbsp;: r\u00e9diger un test d'\u00e9chec, puis \u00e9crire un code minimal pour le faire passer, puis refactoriser. Alors que TDD est bien \u00e9tabli dans les logiciels commerciaux, les projets de recherche y r\u00e9sistent souvent en raison des contraintes de temps per\u00e7ues.<\/p>\n<p>La r\u00e9alit\u00e9 : TDD fait gagner du temps dans la recherche en attrapant des bogues avant de se propager \u00e0 travers des exp\u00e9riences. Les tests d'\u00e9criture vous obligent d'abord \u00e0 clarifier l'interface et le comportement attendu de chaque fonction avant la mise en \u0153uvre.<\/p>\n<p><strong>TDD adapt\u00e9 \u00e0 l'exploration scientifique&nbsp;:<\/strong><\/p>\n<ol>\n<li>Commencez par un mod\u00e8le ou un algorithme simple que vous comprenez analytiquement<\/li>\n<li>R\u00e9diger des tests qui valident par rapport aux r\u00e9sultats connus (solutions analytiques, cas limitant)<\/li>\n<li>Impl\u00e9menter le code pour r\u00e9ussir ces tests<\/li>\n<li>\u00c9tendez le mod\u00e8le de mani\u00e8re incr\u00e9mentielle, en ajoutant des tests pour chaque nouvelle capacit\u00e9<\/li>\n<li>Lorsque vous d\u00e9couvrez un bogue, \u00e9crivez un test qui le reproduit en premier, puis corrigez-le<\/li>\n<\/ol>\n<p>TDD fonctionne bien pour&nbsp;:<\/p>\n<ul>\n<li>Fonctions utilitaires (g\u00e9n\u00e9ration de maillage, transformations de coordonn\u00e9es)<\/li>\n<li>Op\u00e9rations math\u00e9matiques (manipulations matricielles, fonctions sp\u00e9ciales)<\/li>\n<li>Pipelines de traitement de donn\u00e9es (analyse, filtrage, normalisation)<\/li>\n<li>Validation de la configuration<\/li>\n<\/ul>\n<p>TDD est moins adapt\u00e9 pour :<\/p>\n<ul>\n<li>Code hautement exploratoire o\u00f9 l'interface elle-m\u00eame est incertaine<\/li>\n<li>Des scripts uniques qui ne seront pas r\u00e9utilis\u00e9s<\/li>\n<li>Code qui d\u00e9pend de ressources externes non encore disponibles<\/li>\n<\/ul>\n<p>En pratique, une approche hybride fonctionne le mieux : r\u00e9diger des tests pour des composants stables et fondamentaux ; Utilisez des tests d'int\u00e9gration plus l\u00e9gers pour les sections exp\u00e9rimentales.<\/p>\n<h2>Organisation de tests pour des projets scientifiques<\/h2>\n<p>O\u00f9 tester les fichiers ? PyTest offre une flexibilit\u00e9 :<\/p>\n<pre><code>my_research_project\/\n\u251c\u2500\u2500 src\/\n\u2502   \u2514\u2500\u2500 mypackage\/\n\u2502       \u251c\u2500\u2500 __init__.py\n\u2502       \u251c\u2500\u2500 solver.py\n\u2502       \u2514\u2500\u2500 mesh.py\n\u251c\u2500\u2500 tests\/\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u251c\u2500\u2500 test_solver.py\n\u2502   \u251c\u2500\u2500 test_mesh.py\n\u2502   \u2514\u2500\u2500 conftest.py  # shared fixtures\n\u251c\u2500\u2500 data\/\n\u2502   \u2514\u2500\u2500 reference_results\/  # stored outputs for regression tests\n\u251c\u2500\u2500 .github\/\n\u2502   \u2514\u2500\u2500 workflows\/\n\u2502       \u2514\u2500\u2500 ci.yml  # GitHub Actions CI configuration\n\u251c\u2500\u2500 pyproject.toml\n\u2514\u2500\u2500 README.md\n<\/code><\/pre>\n<p><strong>Conventions cl\u00e9s&nbsp;:<\/strong><\/p>\n<ul>\n<li>Conserver les tests dans un r\u00e9pertoire s\u00e9par\u00e9 <code>tests\/<\/code> parall\u00e8le \u00e0 <code>src\/<\/code> (ou <code>lib\/<\/code>)<\/li>\n<li>Nommer les fichiers de test <code>test_*.py<\/code> ou <code>*_test.py<\/code><\/li>\n<li>Nommer les fonctions de test <code>test_*()<\/code> pour autoriser la d\u00e9couverte automatique de PyTest<\/li>\n<li>Utilisez <code>conftest.py<\/code> pour les luminaires partag\u00e9s entre plusieurs fichiers de test<\/li>\n<\/ul>\n<p>Pour les projets bas\u00e9s sur Fipy, structurez les tests pour correspondre \u00e0 la hi\u00e9rarchie des modules&nbsp;:<\/p>\n<pre><code>fipy_project\/\n\u251c\u2500\u2500 fipy\/\n\u2502   \u251c\u2500\u2500 meshes\/\n\u2502   \u2502   \u2514\u2500\u2500 grid1d.py\n\u2502   \u2514\u2500\u2500 terms\/\n\u2502       \u2514\u2500\u2500 diffusion.py\n\u251c\u2500\u2500 tests\/\n\u2502   \u251c\u2500\u2500 meshes\/\n\u2502   \u2502   \u2514\u2500\u2500 test_grid1d.py\n\u2502   \u2514\u2500\u2500 terms\/\n\u2502       \u2514\u2500\u2500 test_diffusion.py\n<\/code><\/pre>\n<h2>Int\u00e9gration avec int\u00e9gration continue<\/h2>\n<p>Les tests unitaires ne fournissent de la valeur que s'ils fonctionnent de mani\u00e8re coh\u00e9rente. Int\u00e9gration continue (CI) Automatise l'ex\u00e9cution des tests chaque fois que le code change.<\/p>\n<p>GitHub Actions fournit une configuration CI simple pour les projets Python&nbsp;:<\/p>\n<pre><code class=\"language-yaml\"># .github\/workflows\/ci.yml\nname: CI\n\non: [push, pull_request]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.9\", \"3.10\", \"3.11\"]\n\n    steps:\n    - uses: actions\/checkout@v3\n    - name: Set up Python ${{ matrix.python-version }}\n      uses: actions\/setup-python@v4\n      with:\n        python-version: ${{ matrix.python-version }}\n    - name: Install dependencies\n      run: |\n        python -m pip install --upgrade pip\n        pip install -e .[test]\n    - name: Run tests with pytest\n      run: |\n        pytest --cov=src --cov-report=xml --cov-report=html\n    - name: Upload coverage\n      uses: codecov\/codecov-action@v3\n<\/code><\/pre>\n<p>CI assure :<\/p>\n<ul>\n<li>Les tests passent sur plusieurs plates-formes (Linux, macOS, Windows)<\/li>\n<li>Les tests r\u00e9ussissent sur plusieurs versions de Python<\/li>\n<li>La couverture du code est suivie dans le temps<\/li>\n<li>Les demandes d'extraction sont valid\u00e9es avant la fusion<\/li>\n<\/ul>\n<p>Pour les logiciels de recherche, pensez \u00e0 ajouter :<\/p>\n<ul>\n<li>Tests qui s'ex\u00e9cutent avec diff\u00e9rentes versions de d\u00e9pendances cl\u00e9s (numpy, scipy, fipy)<\/li>\n<li>V\u00e9rifications de la r\u00e9gression des performances (les algorithmes ne ralentissent pas)<\/li>\n<li>La documentation construite pour v\u00e9rifier les exemples fonctionne toujours<\/li>\n<\/ul>\n<h2>erreurs courantes et comment les \u00e9viter<\/h2>\n<p>Sur la base de la recherche et des meilleures pratiques de l'industrie, voici les erreurs de test unitaire les plus courantes dans le code scientifique&nbsp;:<\/p>\n<h3>1. Tester les d\u00e9tails de l'impl\u00e9mentation au lieu du comportement<\/h3>\n<p>Test de la mise en \u0153uvre interne rend les tests cassants. Lorsque vous refactorisez le code, les tests doivent toujours passer si le comportement externe est correct.<\/p>\n<pre><code class=\"language-python\"># \u274c Bad: tests internal state\ndef test_algorithm_updates_counter():\n    obj = MyAlgorithm()\n    obj.step()\n    assert obj.counter == 1  # fragile if counter implementation changes\n\n# \u2705 Better: tests observable outcome\ndef test_algorithm_produces_correct_result():\n    obj = MyAlgorithm()\n    result = obj.run()\n    assert result == expected\n<\/code><\/pre>\n<h3>2. Ignorer la tol\u00e9rance num\u00e9rique<\/h3>\n<p>Les comparaisons exactes sur les r\u00e9sultats \u00e0 virgule flottante provoquent des tests flous qui \u00e9chouent au hasard ou sur un mat\u00e9riel diff\u00e9rent. Utilisez toujours <code>pytest.approx()<\/code> ou des assertions similaires bas\u00e9es sur la tol\u00e9rance pour les sorties num\u00e9riques.<\/p>\n<h3>3. \u00c9crire des tests lents<\/h3>\n<p>Les tests unitaires doivent fonctionner rapidement (millisecondes, pas secondes). Si un test est lent :<\/p>\n<ul>\n<li>Il ne sera pas ex\u00e9cut\u00e9 assez fr\u00e9quemment<\/li>\n<li>Les d\u00e9veloppeurs ignoreront l'ex\u00e9cution de la suite de tests compl\u00e8te<\/li>\n<li>CI devient cher et lent<\/li>\n<\/ul>\n<p><strong>Solutions&nbsp;:<\/strong><\/p>\n<ul>\n<li>Utilisez de petits ensembles de donn\u00e9es synth\u00e9tiques au lieu de grands r\u00e9els<\/li>\n<li>simuler des calculs externes co\u00fbteux<\/li>\n<li>S\u00e9parez les tests d'int\u00e9gration lente des tests unitaires rapides<\/li>\n<li>Utilisez la param\u00e9trisation \u00e0 bon escient&nbsp;: n'ex\u00e9cutez pas de milliers de variations dans chaque test r\u00e9ussi<\/li>\n<\/ul>\n<h3>4. Ne pas isoler les tests<\/h3>\n<p>Les tests ne doivent pas d\u00e9pendre les uns des autres ou de l'\u00e9tat global. Chaque test doit :<\/p>\n<ul>\n<li>Cr\u00e9ez ses propres donn\u00e9es de test (utilisez des appareils)<\/li>\n<li>Nettoyer apr\u00e8s lui-m\u00eame<\/li>\n<li>ne pas compter sur l'ordre d'ex\u00e9cution<\/li>\n<\/ul>\n<pre><code class=\"language-python\"># \u274c Bad: shared mutable state\nresults = []\n\ndef test_first():\n    results.append(1)\n\ndef test_second():\n    assert results == [1]  # fails if tests run in wrong order\n\n# \u2705 Better: independent tests\ndef test_first():\n    result = compute_something()\n    assert result == 1\n\ndef test_second():\n    result = compute_something_else()\n    assert result == 2\n<\/code><\/pre>\n<h3>5. Sauter les tests sans raison valable<\/h3>\n<p><code>@pytest.mark.skip<\/code> doit \u00eatre utilis\u00e9 avec parcimonie. Si un test est ignor\u00e9 parce que l'environnement manque de quelque chose, utilisez <code>pytest.importorskip()<\/code> au niveau du module ou rendez la d\u00e9pendance facultative dans la configuration CI.<\/p>\n<h3>6. \u00c9crire de vagues assertions<\/h3>\n<p>Les tests doivent exprimer clairement ce qui est v\u00e9rifi\u00e9 et pourquoi.<\/p>\n<pre><code class=\"language-python\"># \u274c Unclear: what's being tested?\nassert result != None\n\n# \u2705 Clear: specific expectation with context\nassert result.converged is True, \"Solver should converge for well-posed problem\"\n<\/code><\/pre>\n<h3>7. Chemins de codage en dur et hypoth\u00e8ses d'environnement<\/h3>\n<p>Utilisez des r\u00e9pertoires et des luminaires temporaires plut\u00f4t que des chemins fixes. Le luminaire <code>tmp_path<\/code> de PyTest fournit un nouveau r\u00e9pertoire temporaire pour chaque test.<\/p>\n<h2>Exemple pratique : test d'un solveur de diffusion Fipy<\/h2>\n<p>Rassemblons ces strat\u00e9gies avec un exemple concret pertinent pour le public de Matforge.<\/p>\n<pre><code class=\"language-python\"># tests\/test_diffusion.py\nimport pytest\nimport numpy as np\nfrom fipy import Grid1D, CellVariable, DiffusionTerm\n\n@pytest.fixture\ndef simple_1d_grid():\n    \"\"\"Create a uniform 1D grid for diffusion testing.\"\"\"\n    return Grid1D(dx=0.1, nx=50)\n\n@pytest.fixture\ndef steady_state_diffusion(simple_1d_grid):\n    \"\"\"Set up diffusion with Dirichlet boundaries at both ends.\"\"\"\n    mesh = simple_1d_grid\n    var = CellVariable(name=\"concentration\", mesh=mesh, value=0.0)\n    var.constrain(1.0, mesh.facesLeft)\n    var.constrain(0.0, mesh.facesRight)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    return var, eq\n\ndef test_mesh_volume(simple_1d_grid):\n    \"\"\"Total domain length should equal nx * dx.\"\"\"\n    expected_length = 50 * 0.1\n    assert simple_1d_grid.cellVolumes.sum() == pytest.approx(expected_length)\n\ndef test_diffusion_conservation(steady_state_diffusion):\n    \"\"\"For steady diffusion with no sources, total mass should be conserved.\"\"\"\n    var, eq = steady_state_diffusion\n    eq.solve(var, dt=1.0)\n    # With fixed values at boundaries, mass enters from left and exits right\n    # In steady state, flux in should equal flux out\n    left_flux = var.faceValue[simple_1d_grid.facesLeft.value]\n    right_flux = var.faceValue[simple_1d_grid.facesRight.value]\n    # Flux direction: positive means flow to the right\n    assert left_flux &gt; 0  # mass enters from left\n    assert right_flux &lt; 0  # mass exits from right (negative direction)\n    assert abs(left_flux + right_flux) == pytest.approx(0, abs=1e-10)\n\ndef test_diffusion_solution_shape(steady_state_diffusion):\n    \"\"\"Concentration should decrease monotonically from left to right.\"\"\"\n    var, eq = steady_state_diffusion\n    eq.solve(var, dt=1.0)\n    # Steady state should be linear for constant diffusivity\n    x = simple_1d_grid.cellCenters[0]\n    expected = 1.0 - x \/ (50 * 0.1)  # linear from 1 to 0\n    assert var.value == pytest.approx(expected, rel=1e-5)\n\n@pytest.mark.parametrize(\"dx,nx\", [(0.1, 50), (0.05, 100), (0.02, 250)])\ndef test_mesh_independence(dx, nx):\n    \"\"\"Solution should converge as mesh refines.\"\"\"\n    mesh = Grid1D(dx=dx, nx=nx)\n    var = CellVariable(name=\"c\", mesh=mesh, value=0.0)\n    var.constrain(1.0, mesh.facesLeft)\n    var.constrain(0.0, mesh.facesRight)\n    eq = DiffusionTerm(coeff=1.0) == 0\n    eq.solve(var, dt=1.0)\n    # Check at midpoint\n    mid_idx = nx \/\/ 2\n    assert var.value[mid_idx] == pytest.approx(0.5, rel=0.1)\n<\/code><\/pre>\n<p>Cet exemple illustre :<\/p>\n<ul>\n<li>Appareils pour une configuration de test r\u00e9utilisable<\/li>\n<li>Param\u00e9trage pour tester plusieurs r\u00e9solutions<\/li>\n<li>Affirmations num\u00e9riques bas\u00e9es sur la tol\u00e9rance<\/li>\n<li>Tester les principes physiques (conservation, lin\u00e9arit\u00e9)<\/li>\n<li>Noms et assertions de test clairs et descriptifs<\/li>\n<\/ul>\n<h2>Lorsque les tests unitaires ne suffisent pas<\/h2>\n<p>Les tests unitaires valident des composants individuels, mais les logiciels de recherche ont \u00e9galement besoin :<\/p>\n<ul>\n<li><strong>Tests d'int\u00e9gration<\/strong>&nbsp;: v\u00e9rifiez que plusieurs modules fonctionnent correctement ensemble<\/li>\n<li><strong>Tests de syst\u00e8me<\/strong>&nbsp;: ex\u00e9cuter des simulations compl\u00e8tes de bout en bout et comparer aux sorties connues<\/li>\n<li><strong>Tests de performance<\/strong>&nbsp;: s'assurer que les algorithmes r\u00e9pondent aux attentes de complexit\u00e9 de calcul<\/li>\n<li><strong>V\u00e9rifications de visualisation<\/strong>&nbsp;: rep\u00e9rer les erreurs de rendu \u00e9videntes (comparaison d'images automatis\u00e9e si possible)<\/li>\n<\/ul>\n<p>Une strat\u00e9gie de test compl\u00e8te pour les projets de recherche comprend plusieurs niveaux de test, les tests unitaires formant la base.<\/p>\n<h2>Ce que nous recommandons : une strat\u00e9gie de test pragmatique pour des projets de recherche<\/h2>\n<p>Sur la base des preuves tir\u00e9es des meilleures pratiques de logiciels scientifiques, voici notre approche recommand\u00e9e&nbsp;:<\/p>\n<h3>Commencez par des tests unitaires fondamentaux<\/h3>\n<p>Commencez par \u00e9crire des tests pour&nbsp;:<\/p>\n<ul>\n<li>Fonctions math\u00e9matiques de base (fonctions sp\u00e9ciales, transformations de coordonn\u00e9es)<\/li>\n<li>Utilitaires de g\u00e9n\u00e9ration et de manipulation de maillage<\/li>\n<li>Impl\u00e9mentations des conditions aux limites<\/li>\n<li>Routines d'entr\u00e9e\/sortie de donn\u00e9es (validation, mise en forme)<\/li>\n<\/ul>\n<p>Ces composants sont stables, ont des comportements clairs attendus et sont r\u00e9utilis\u00e9s dans de nombreuses simulations.<\/p>\n<h3>Adoptez pytest.approx en standard<\/h3>\n<p>N'utilisez jamais <code>==<\/code> pour les r\u00e9sultats \u00e0 virgule flottante. Utilisez toujours <code>pytest.approx()<\/code> avec les tol\u00e9rances appropri\u00e9es. Faites-en une convention d'\u00e9quipe.<\/p>\n<h3>Utiliser largement les luminaires<\/h3>\n<p>Les luminaires r\u00e9duisent la duplication et rendent les tests plus maintenables. Cr\u00e9er des luminaires pour&nbsp;:<\/p>\n<ul>\n<li>Maillages communs (grille de test 1D, 2D, 3D)<\/li>\n<li>Configuration des conditions aux limites standard<\/li>\n<li>Solutions d'analyse connues<\/li>\n<li>Gestion des fichiers\/r\u00e9pertoires temporaires<\/li>\n<\/ul>\n<h3>Int\u00e9grer CI t\u00f4t<\/h3>\n<p>Configurez des actions GitHub (ou similaires) avant que le projet ne devienne grand. Ex\u00e9cutez automatiquement des tests sur&nbsp;:<\/p>\n<ul>\n<li>Chaque pouss\u00e9e<\/li>\n<li>Chaque demande d'extraction<\/li>\n<li>Constructions nocturnes pr\u00e9vues (pour attraper la d\u00e9rive environnementale)<\/li>\n<\/ul>\n<h3>Mesurer et suivre la couverture du code<\/h3>\n<p>Utilisez <code>pytest-cov<\/code> pour mesurer quelles parties de votre code sont exerc\u00e9es par des tests. Visez au moins 80&nbsp;% de couverture sur les modules de base, mais ne vous obs\u00e9dez pas \u00e0 plus de 100&nbsp;%&nbsp;: l'objectif est la confiance, pas un score parfait.<\/p>\n<h3>R\u00e9digez des tests lorsque vous corrigez des bogues<\/h3>\n<p>Chaque fois qu'un bogue est signal\u00e9, \u00e9crivez un test qui le reproduit avant de corriger. Cela garantit que le bogue ne r\u00e9appara\u00eetra pas plus tard.<\/p>\n<h3>Gardez les tests rapides<\/h3>\n<p>Si un test dure plus de quelques secondes, pensez \u00e0 :<\/p>\n<ul>\n<li>Utilisation de petits probl\u00e8mes de test<\/li>\n<li>Se moquer des op\u00e9rations co\u00fbteuses<\/li>\n<li>D\u00e9placement vers une suite de tests d'int\u00e9gration qui s'ex\u00e9cute moins fr\u00e9quemment<\/li>\n<\/ul>\n<h2>Guides connexes<\/h2>\n<ul>\n<li><a href=\"\/tracking-long-term-technical-debt-in-research-software\/\">Suivi de la dette technique \u00e0 long terme dans les logiciels de recherche<\/a> \u2013 G\u00e9rer la dette de test \u00e0 mesure que les projets \u00e9voluent<\/li>\n<li><a href=\"\/managing-research-software-through-tickets\/\">Gestion des logiciels de recherche via des tickets<\/a> \u2013 Utilisation du suivi des probl\u00e8mes pour coordonner les efforts de test<\/li>\n<li><a href=\"\/reproducibility-and-its-role-in-debugging\/\">Reproductibilit\u00e9 et son r\u00f4le dans le d\u00e9bogage<\/a> \u2013 Comment les tests permettent le d\u00e9bogage syst\u00e9matique<\/li>\n<li><a href=\"\/how-to-write-a-clear-and-useful-bug-report\/\">comment r\u00e9diger un rapport de bogue clair et utile<\/a> \u2013 Fournir les informations n\u00e9cessaires pour cr\u00e9er des tests de r\u00e9gression<\/li>\n<li><a href=\"\/feature-requests-vs-bug-reports-knowing-the-difference\/\">demandes de fonctionnalit\u00e9s vs. Rapports de bogues&nbsp;: conna\u00eetre la diff\u00e9rence<\/a> \u2013 Classification des probl\u00e8mes qui entra\u00eenent le d\u00e9veloppement des tests<\/li>\n<\/ul>\n<h2>Conclusion<\/h2>\n<p>Les tests unitaires transforment les logiciels de recherche \u00e0 partir de scripts fragiles en instruments fiables et reproductibles. Bien que la mise en place de tests complets n\u00e9cessite un investissement initial, le gain s'explique par une r\u00e9duction du temps de d\u00e9bogage, une confiance accrue dans les r\u00e9sultats et une collaboration plus fluide.<\/p>\n<p>Le framework PyTest fournit des outils puissants - am\u00e9nagements, param\u00e9trage, moquerie et <code>approx()<\/code> - qui r\u00e9pondent directement aux d\u00e9fis du code scientifique&nbsp;: pr\u00e9cision num\u00e9rique, d\u00e9pendances externes et r\u00e9ponses correctes inconnues. Associ\u00e9es \u00e0 une int\u00e9gration continue, ces pratiques garantissent que les tests s'ex\u00e9cutent de mani\u00e8re coh\u00e9rente dans tous les environnements.<\/p>\n<p>N'oubliez pas que les tests ne consistent pas \u00e0 atteindre la perfection. Il s'agit de renforcer suffisamment la confiance dans votre code pour que vous puissiez faire confiance \u00e0 ses r\u00e9sultats lorsque cela compte le plus. Commencez par les composants de base, r\u00e9digez des tests qui expriment des attentes claires et \u00e9largissent progressivement la couverture au fur et \u00e0 mesure que le projet se d\u00e9veloppe.<\/p>\n<p>Votre futur moi et toute personne qui h\u00e9rite de votre code vous remerciera.<\/p>\n<h2>Prochaines \u00e9tapes<\/h2>\n<p>Pr\u00eat \u00e0 ajouter des tests \u00e0 votre projet de recherche&nbsp;?<\/p>\n<ol>\n<li>Installer PyTest&nbsp;: <code>pip install pytest<\/code><\/li>\n<li>Cr\u00e9er un r\u00e9pertoire <code>tests\/<\/code> avec un simple fichier de test<\/li>\n<li>R\u00e9digez un test pour une fonction de base en utilisant <code>pytest.approx<\/code><\/li>\n<li>Configurer un workflow d'actions GitHub pour ex\u00e9cuter des tests automatiquement<\/li>\n<li>\u00c9largissez progressivement la couverture lorsque vous modifiez le code<\/li>\n<\/ol>\n<p>Pour une aide personnalis\u00e9e \u00e0 la mise en \u0153uvre de strat\u00e9gies de test dans votre logiciel de recherche sp\u00e9cifique, <a href=\"\/\">nous contacter pour une consultation<\/a> (visitez notre page d'accueil pour plus d'informations).<\/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\"> 12<\/span> <span class=\"rt-label rt-postfix\">minutes<\/span><\/span>Les tests unitaires ne sont pas n\u00e9gociables pour les logiciels scientifiques fiables. Contrairement aux applications commerciales, le code de recherche manque souvent de tests formels, ce qui conduit \u00e0 des r\u00e9sultats irr\u00e9productibles et \u00e0 un effort inutile. Ce guide couvre les strat\u00e9gies PyTest sp\u00e9cifiquement pour les projets Python scientifiques&nbsp;: gestion de la pr\u00e9cision num\u00e9rique avec [&hellip;]<\/p>\n","protected":false,"raw":""},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_locale":"fr_FR","_original_post":"https:\/\/matforge.org\/?p=187","iawp_total_views":1,"footnotes":""},"categories":[3],"tags":[],"class_list":["post-1238","post","type-post","status-publish","format-standard","hentry","category-issue-tracking-tickets-technical-requests","fr-FR"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v28.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org<\/title>\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\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org\" \/>\n<meta property=\"og:description\" content=\"Reading Time:  12 minutesLes tests unitaires ne sont pas n\u00e9gociables pour les logiciels scientifiques fiables. Contrairement aux applications commerciales, le code de recherche manque souvent de tests formels, ce qui conduit \u00e0 des r\u00e9sultats irr\u00e9productibles et \u00e0 un effort inutile. Ce guide couvre les strat\u00e9gies PyTest sp\u00e9cifiquement pour les projets Python scientifiques&nbsp;: gestion de la pr\u00e9cision num\u00e9rique avec [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/\" \/>\n<meta property=\"og:site_name\" content=\"matforge.org\" \/>\n<meta property=\"article:published_time\" content=\"2026-08-21T14:28:36+00:00\" \/>\n<meta name=\"author\" content=\"Priya Nair\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"\u00c9crit par\" \/>\n\t<meta name=\"twitter:data1\" content=\"Priya Nair\" \/>\n\t<meta name=\"twitter:label2\" content=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data2\" content=\"19 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/\"},\"author\":{\"name\":\"Priya Nair\",\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/2effd7bc155a5e6357f31dac970c5795\"},\"headline\":\"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche\",\"datePublished\":\"2026-08-21T14:28:36+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/\"},\"wordCount\":3018,\"commentCount\":0,\"articleSection\":[\"Suivi des probl\u00e8mes, billets &amp; Demandes techniques\"],\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/\",\"url\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/\",\"name\":\"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/#website\"},\"datePublished\":\"2026-08-21T14:28:36+00:00\",\"author\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/2effd7bc155a5e6357f31dac970c5795\"},\"breadcrumb\":{\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/matforge.org\\\/fr\\\/unit-testing-scientific-code-pytest-strategies-research-projects\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/matforge.org\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche\"}]},{\"@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\":\"fr-FR\"},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/matforge.org\\\/#\\\/schema\\\/person\\\/2effd7bc155a5e6357f31dac970c5795\",\"name\":\"Priya Nair\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g\",\"caption\":\"Priya Nair\"},\"sameAs\":[\"http:\\\/\\\/matforge.org\"],\"url\":\"https:\\\/\\\/matforge.org\\\/author\\\/priya-nair\\\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org","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\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/","og_locale":"fr_FR","og_type":"article","og_title":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org","og_description":"Reading Time:  12 minutesLes tests unitaires ne sont pas n\u00e9gociables pour les logiciels scientifiques fiables. Contrairement aux applications commerciales, le code de recherche manque souvent de tests formels, ce qui conduit \u00e0 des r\u00e9sultats irr\u00e9productibles et \u00e0 un effort inutile. Ce guide couvre les strat\u00e9gies PyTest sp\u00e9cifiquement pour les projets Python scientifiques&nbsp;: gestion de la pr\u00e9cision num\u00e9rique avec [&hellip;]","og_url":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/","og_site_name":"matforge.org","article_published_time":"2026-08-21T14:28:36+00:00","author":"Priya Nair","twitter_card":"summary_large_image","twitter_misc":{"\u00c9crit par":"Priya Nair","Dur\u00e9e de lecture estim\u00e9e":"19 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/#article","isPartOf":{"@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/"},"author":{"name":"Priya Nair","@id":"https:\/\/matforge.org\/#\/schema\/person\/2effd7bc155a5e6357f31dac970c5795"},"headline":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche","datePublished":"2026-08-21T14:28:36+00:00","mainEntityOfPage":{"@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/"},"wordCount":3018,"commentCount":0,"articleSection":["Suivi des probl\u00e8mes, billets &amp; Demandes techniques"],"inLanguage":"fr-FR","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/","url":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/","name":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche - matforge.org","isPartOf":{"@id":"https:\/\/matforge.org\/#website"},"datePublished":"2026-08-21T14:28:36+00:00","author":{"@id":"https:\/\/matforge.org\/#\/schema\/person\/2effd7bc155a5e6357f31dac970c5795"},"breadcrumb":{"@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/matforge.org\/fr\/unit-testing-scientific-code-pytest-strategies-research-projects\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/matforge.org\/"},{"@type":"ListItem","position":2,"name":"Tests unitaires pour le code scientifique : strat\u00e9gies Pytest pour des projets de recherche"}]},{"@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":"fr-FR"},{"@type":"Person","@id":"https:\/\/matforge.org\/#\/schema\/person\/2effd7bc155a5e6357f31dac970c5795","name":"Priya Nair","image":{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/secure.gravatar.com\/avatar\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f11e168d4cd2f1eff83cbb851d6cff42d81d88bb59d8831ee77468aa4a5eea88?s=96&d=mm&r=g","caption":"Priya Nair"},"sameAs":["http:\/\/matforge.org"],"url":"https:\/\/matforge.org\/author\/priya-nair\/"}]}},"_links":{"self":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/1238","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\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/comments?post=1238"}],"version-history":[{"count":1,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/1238\/revisions"}],"predecessor-version":[{"id":1352,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/posts\/1238\/revisions\/1352"}],"wp:attachment":[{"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/media?parent=1238"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/categories?post=1238"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/matforge.org\/wp-json\/wp\/v2\/tags?post=1238"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}