Bonjour marion_dev. Trois pratiques qui, combinées, ont réglé le problème pour nous sur une API PostgreSQL d'une centaine de tables.
Une base jetable par test (ou par fichier de tests, selon la granularité qui vous convient). Avec PostgreSQL, CREATE DATABASE test_42 TEMPLATE test_modele est quasi instantané, parce que c'est une copie de fichiers et non un rejeu de migrations. On prépare une base modèle une seule fois au démarrage de la suite (migrations, puis données de référence), et chaque test se voit attribuer un clone nommé avec un identifiant unique, qu'il supprime à la fin. Ça rend la parallélisation triviale : chaque processus de test a sa propre base, aucune interférence possible. Le coût est de quelques dizaines de millisecondes par clone. Ça répond aussi à la remarque de dbjulien : les migrations sont rejouées une fois par suite, pas une fois par test.
Des transactions annulées pour les tests qui n'ont besoin que d'une connexion. Pour la majorité des tests, plus légers, on ouvre une transaction avant, on exécute le test dedans, et on fait un ROLLBACK après, quoi qu'il arrive. Aucun nettoyage à écrire, la base revient exactement à son état initial. La limite est bien connue : ça ne marche pas si le code testé ouvre lui-même des transactions ou utilise plusieurs connexions (un pool, une file de travail). Pour ces cas-là, on retombe sur la base jetable du point précédent ; c'est précisément pour ça qu'on garde les deux mécanismes côte à côte.
Des données de référence figées et versionnées. La base modèle contient un jeu de données minimal mais réaliste, généré par un script versionné avec le code, jamais par une copie de la production. Chaque test qui a besoin de données particulières les crée lui-même à partir de fabriques, sur ce socle commun. On a arrêté d'avoir des tests qui dépendent d'une ligne « client 17 » que personne ne se souvient avoir insérée.
Pour ton problème de tests laissés à moitié : avec le rollback systématique, un test qui échoue ne laisse rien derrière lui ; et avec les clones, même un test qui plante brutalement ne pollue que sa propre base, qu'on supprime en masse au démarrage suivant (les noms suivent un préfixe, une boucle de DROP DATABASE suffit).