Hypothesis

Laissez la machine trouver vos edge cases

Test unitaire

          def add(a, b):
              return a + b


          def test_add():
              assert add(2, 3) == 5
              assert add(-1, 1) == 0
        
Test end-to-end (api)

          def test_create_article(client):
              token = login(client, "alice")
              resp = client.post(
                  "/api/articles",
                  json={"title": "Hello", "body": "..."},
                  headers={"Authorization": f"Token {token}"},
              )
              assert resp.status_code == 201
              assert resp.json()["article"]["slug"] == "hello"
        
Test end-to-end (ui) — Playwright

          def test_publish_article(page):
              page.goto("https://demo.realworld.show")
              page.get_by_text("New Article").click()
              page.get_by_placeholder("Article Title").fill("Hello")
              page.get_by_placeholder("Write your article (in markdown)").fill("...")
              page.get_by_text("Publish Article").click()

              expect(page.locator("h1")).to_have_text("Hello")
        
E2E Intégration Unitaires

Mon rapport au test

Avant l'agentique Après l'agentique
Projets testés en fonction du projet tout, sauf le dogfood
Couverture « ça dépend » 100 %

Ce que je teste

  • Exemple : mainteneur de RealWorld
    • Hurl / Bruno / Playwright
  • Mes propres utilitaires CLI en Python…
    • dont je ne mesure même pas la couverture
Suffisant pour les cas simples (CRUD, outils codés à l'usage).

Et si la machine choisissait ?

Tests classiques
l'utilisateur choisit chaque entrée
Fuzzing
la machine génère, cherche les crashs
Méthodes formelles
on prouve pour toutes les entrées
Property-based testing = quelque part au milieu…
Qui choisit les entrées la machine
Oracle crash uniquement
Domaine typique parsers, navigateurs, sécurité
Le fuzzer ne sait pas ce qui est correct — juste ce qui crash.
Couverture toutes les entrées (preuve)
Coût élevé
Domaine typique aérospatial, crypto, hardware, smart contracts
Garantie maximale, applicabilité étroite, budget conséquent.
Lean — une preuve, pas un échantillon

          -- Vrai pour TOUT a, b : aucune valeur n'est échantillonnée
          -- la preuve est vérifiée à la compilation
          theorem add_comm (a b : Nat) : a + b = b + a := by
            omega
        
Pas de cas d'essai : le compilateur vérifie la preuve, ou refuse de compiler.

Property-based testing

Vous écrivez des propriétés (comme le formel)
+ la machine génère & réduit les entrées (comme le fuzzing)
« du fuzzing qui sait ce qui est correct »
Tests classiques Fuzzing PBT Formel
Entrées valeurs idéalement en dur machine machine (stratégies) toutes (symbolique)
Oracle assertions précises crash only propriétés spécification
Domaine partout parsers, sécu libs, data, métier aéro, crypto
Coût faible moyen faible–moyen élevé
ROI ? ? ? ?
Hypothesis

          from hypothesis import given, strategies as st


          @given(st.integers(), st.integers())
          def test_addition_commutative(a, b):
              assert add(a, b) == add(b, a)
        
Pas un tirage uniforme

          @given(st.integers())
          def test_p(n):
              ...

          # Vise les bornes de l'intervalle, et injecte des constantes "SWE" :
          #   2**31 - 1, 2**63 - 1     # overflow signé 32 / 64 bits
          #   2**16, 10**9, 20!        # puissances de 2, de 10, factorielles
          #   510510, 6469693230       # primorielles (codées en dur !)
        
GLOBAL_CONSTANTS — providers.py
Composer des structures

          jsons = st.recursive(
              st.none() | st.booleans() | st.integers() | st.text(),
              lambda children: (
                  st.lists(children)
                  | st.dictionaries(st.text(), children)
              ),
          )

          @given(jsons)
          def test_roundtrip(value):
              assert json.loads(json.dumps(value)) == value
        
Listes, dicts, récursion : les stratégies se composent.
La réduction (shrinking)

          @given(st.lists(st.integers()))
          def test_sort_idempotent(xs):
              assert my_sort(my_sort(xs)) == my_sort(xs)

          # Falsifying example: xs=[0]
          # (réduit automatiquement depuis [-42, 1000, 0, 7, ...])
        
le shrinker d'Hypothesis
Rejouer le dernier contre-exemple
  • Le contre-exemple réduit est sauvegardé
  • Répertoire .hypothesis/ — la base d'exemples
  • Au prochain run, il est rejoué en premier
  • Partageable (CI, base partagée)
Aller-retour (round-trip)

          @given(st.text())
          def test_json_roundtrip(s):
              assert json.loads(json.dumps(s)) == s
        
Implémentation de référence

          @given(st.lists(st.integers()))
          def test_against_reference(xs):
              assert my_fast_sort(xs) == sorted(xs)
        
Le code lent & évident est l'oracle du code rapide & subtil.
Invariants — « rien ne change »

          @given(st.lists(st.integers()))
          def test_sort_preserves(xs):
              ys = my_sort(xs)
              assert len(ys) == len(xs)        # même longueur
              assert set(ys) == set(xs)        # mêmes éléments
        
On ne dit pas quel résultat ; juste ce qui doit rester vrai.
Idempotence — deux fois = une fois

          @given(st.text())
          def test_normalize_idempotent(s):
              once = normalize(s)
              assert normalize(once) == once
        
Réappliquer l'opération ne change plus rien.

          class ShoppingCart(RuleBasedStateMachine):

              @rule(item=st.text(), qty=st.integers(min_value=1))
              def add(self, item, qty):
                  ...

              @rule()
              def checkout(self):
                  ...

              @invariant()
              def total_is_never_negative(self):
                  assert self.cart.total() >= 0
        

Et l'IA dans tout ça ?

Un agent LLM qui infère les propriétés depuis le code & la doc,
génère des PBT, confirme les vrais bugs.
Packages Python analysés 100
Rapports = vrais bugs 56 %
Bugs corrigés en amont NumPy, SDKs cloud… patchs mergés
Agentic Property-Based Testing — Maaz, DeVoe, Hatfield-Dodds, Carlini · arXiv:2510.09907

Avant la vague Mythos

Oct. 2025 Des agents trouvent — et corrigent — de vrais bugs via PBT (NumPy, SDKs)
Avr. 2026 Claude Mythos : découverte de bugs en autonomie (sans forcément générer de tests) — des milliers de 0-days en sécu offensive
Le papier PBT précède de 6 mois la vague de vulnérabilités Mythos.
Claude MythosProjet Glasswing

Et après ? — Hegel

Hypothesis (thèse)
+ Antithesis (antithèse)
= Hegel (synthèse)
antithesis.com/blog/2026/hegel
« Haskell programmers are willing to put up with a lot of suffering for correctness. If Python programmers were willing to put up with suffering to achieve correctness, they'd not be writing Python in the first place! »
— David MacIver · hegel.dev