Makina Blog

Le blog Makina-corpus

Optimiser ses tests unitaires Django avec setUpTestData


Découvrez comment gagner en efficacité sur les tests unitaires et sa méthode d'initialisation des tests : setUpTestData.

Django intègre une méthode permettant de créer les données une seul fois pour tout une série de tests : setUpTestData. Nous avons ainsi pu passer de 180 secondes à 90 secondes en mettant nos tests à jours sur un de nos projet. Nous allons voir ici comment utiliser et mettre en place cette fonctionnalité sur de nouveaux tests ou même des tests déjà existants.

Partons du modèle suivant :

class Person(models.Model):
    first_name = models.CharField(max_length=30)
    last_name = models.CharField(max_length=30)

Et des tests suivants :

class PersonTest(TestCase):
    def setUp(self):
        self.alice = Person.objects.create(first_name='Alice', last_name='Smith')
        self.bob = Person.objects.create(first_name='Bob', last_name='Smith')

    def test_alice_first_name(self):
        self.assertEqual(self.alice.first_name, 'Alice')

    def test_bob_first_name(self):
        self.assertEqual(self.bob.first_name, 'Bob')

    def test_bob_first_name_modified(self):
       self.bob.first_name = 'Jack'
        self.bob.save()
        self.assertEqual(self.bob.first_name, 'Jack')

Nous avons donc deux requêtes exécutées avant chaque test. Ce qui nous fais un total de six requêtes effectuées juste pour créer les donnés nécessaires aux tests. setUpTestData utilise les fonctionnalités des transactions des bases de données et nous permettrais de n'exécuter les requêtes qu'une seul fois pour tout les tests. setUpTestData est une méthode de classe et son utilisation est donc un peu différente de setUp :

class PersonTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.alice = Person.objects.create(first_name='Alice', last_name='Smith')
        cls.bob = Person.objects.create(first_name='Bob', last_name='Smith')

    def test_alice_first_name(self):
        self.assertEqual(self.alice.first_name, 'Alice')

    def test_bob_first_name(self):
        self.assertEqual(self.bob.first_name, 'Bob')

    def test_bob_first_name_modified(self):
       self.bob.first_name = 'Jack'
        self.bob.save()
        self.assertEqual(self.bob.first_name, 'Jack')

Les requêtes de création ne seront donc désormais exécutées qu'une seul fois. Mais il reste un autre problème si vous utilisez une version de Django plus ancienne que la 3.2. Si nous faisons tourner les tests tels quels, nous nous rendrions compte que self.bob reste modifié après le test test_bob_first_name_modified. Ceci est du au fait que l'objet self.bob est modifié en mémoire et n'est pas restauré après le test (ce qui est fait automatiquement à partir de Django 3.2). Nous pouvons faire en sorte de recharger les objet avant chaque test à l'aide de setUp et d'une autre fonctionnalité de Django : refresh_from_db.

class PersonTest(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.alice = Person.objects.create(first_name='Alice', last_name='Smith')
        cls.bob = Person.objects.create(first_name='Bob', last_name='Smith')

    def setUp(self):
        self.alice.refresh_from_db()
        self.bob.refresh_from_db()

    def test_alice_first_name(self):
        self.assertEqual(self.alice.first_name, 'Alice')

    def test_bob_first_name(self):
        self.assertEqual(self.bob.first_name, 'Bob')

    def test_bob_first_name_modified(self):
        self.bob.first_name = 'Jack'
        self.bob.save()
        self.assertEqual(self.bob.first_name, 'Jack')

Même si cela fait deux requêtes en plus à chaque test, ce sont des opérations de lecture et non d'écriture, et donc beaucoup plus rapide.

Il est finalement très simple de mettre en place cette nouvelle fonctionnalité, que ce soit pour de nouveaux tests ou d'anciens, permettant un gain de temps assez important.

Actualités en lien

Débo­guer des trig­gers SQL en cascade : une approche visuelle avec Matplot­lib

18/02/2025

Dans cet article, je vais parta­ger mon expé­rience de débo­gage à l’aide de Matplot­lib, un outil de visua­li­sa­tion Python puis­sant et flexible.
Voir l'article
Image
SQL-Matplotlib

Calcu­­lez sur GPU avec Python – Partie 2/3

11/02/2025

Dans cette partie, vous appren­drez à utili­ser votre GPU avec les librai­ries CuPy et PyCUDA. Vous commen­ce­rez à comprendre dans quelles condi­tions un GPU est préfé­rable à un CPU.
Voir l'article
Image
Cartes graphiques - GPU

Calcu­lez sur GPU avec Python – Partie 1/3

04/02/2025

Cet article vous présente comment utili­ser des GPU avec Python en passant par la présen­ta­tion du choix du maté­riel jusqu’à sa mise en œuvre avec diffé­rentes librai­ries : Cupy, cuDF, xarray…
Voir l'article
Image
Visuel Python

Inscription à la newsletter

Nous vous avons convaincus