Blog // Exirel.me

Retrouvez tous les articles liés au tag SQL via le flux rss dédié à ce tag.

Tester une IntegrityError pour un modèle Django

Par Florian Strzelecki - 11:05 - 10.09.2014

Tags : Django, SQL, Unit Testing, Postgres, IntegrityError

Quoi de plus agaçant qu'avoir du code fonctionnel "en vrai", mais dont les tests ne passent pas ? C'est ce qui m'est arrivé hier, alors que je voulais optimiser l'usage de requêtes SQL avec Postgres (solution finalement trouvée avec l'aide de @twidi, un monsieur qui fait des trucs très cool).

Le problème est assez simple : il s'agit de vérifier que votre code lève une IntegrityError lorsqu'une opération ne respecte pas une contrainte d'intégrité, comme une contrainte de clé étrangère invalide sur un item.save(). Prenons les modèles suivant très simple :

from django.db import models

class Category(models.Model):
    title = models.CharField(max_length=118)

class Item(models.Model):
    title = models.CharField(max_length=118)
    category = models.ForeignKey(Category)

La première chose à faire est d'utiliser transaction.atomic autour de l'appel à la méthode save, dans votre code de l'application (et non pas dans le code de vos tests) :

def some_function(invalid_category_pk):
    """The PK is invalid here. No. Matter. What."""
    item = Item(category_id=invalid_category_pk)

    with transaction.atomic():
        item.save()

Si vous utilisez la commande shell, vous verrez l'exception IntegrityError, et passerez le cœur léger à l'écriture des tests unitaires.

Oui, mais.

Si comme moi vous cherchez une classe de base fournie par Django, vous aurez probablement envie d'utiliser TestCase. Cela tombe bien, c'est une sous-classe de TransactionTestCase. Cela devrait fonctionner. N'est-ce pas ? Hein ?

Non. Oh que non.

Aussi étrange que cela m'est apparu au premier abord, si vous utilisez TestCase, cela ne fonctionnera pas : cette classe étend TransactionTestCase spécifiquement pour retirer la gestion des transactions. Oui, personnellement, je trouve cela perturbant et trompeur, et je laisse les mauvaises langues dire que mon avis est biaisé par le temps perdu à découvrir et comprendre cela.

Donc, pour tester cette fonction, il suffit de faire ceci :

from django.db import IntegrityError
from django.test.testcases import TransactionTestCase

class TestFunctions(TransactionTestCase):

    def test_some_function_raise(self):
        """Assert some_function raise when category does not exist"""
        with self.assertRaises(IntegrityError):
            some_function(7814567)

Et voilà ! C'était facile. J'ai perdu des heures pour découvrir ça. Fuck my life.

Au passage, voici la docstring de TestCase :

Does basically the same as TransactionTestCase, but surrounds every test
with a transaction, monkey-patches the real transaction management routines
to do nothing, and rollsback the test transaction at the end of the test.
You have to use TransactionTestCase, if you need transaction management
inside a test.

Multi-Db avec Zend

Par Florian Strzelecki - 20:20 - 07.10.2011

Tags : loldev, Framework, Web, Zend Framework, Technique, Documentation, Programmation, SQL, PHP

Depuis que je travaille avec le Zend Framework (et ce n'est définitivement pas par passion ni envie), je ne passe pas une semaine sans avoir besoin d'aller voir dans le code source du framework pour comprendre ce qu'il fait, pourquoi, comment, et en quel honneur.

Et généralement, je me marre - enfin pas vraiment, mais faites comme si.

Cette semaine pour le #loldev du vendredi, c'est la documentation qui m'a donné l'info qui me manquait pour résoudre un problème qui n'est pas trivial à l'origine, mais qui devrait l'être avec un framework web digne de ce nom : comment gérer une application qui doit se connecter à différentes bases de données ?

En voilà une question intéressante... voici ma réponse.

Pages and pages of source code.

Image : Pages and pages of source code. - Neil Crosby (http://www.flickr.com/photos/thevoicewithin/) - Creative-Common By-NC-SA

Boucle et SQL (et ORM) : la petite erreur à éviter

Par Florian Strzelecki - 18:51 - 11.04.2011

Tags : Programmation, Bonne pratique, PHP, Optimisation, ORM, SQL, Technique

Petite précaution avant d'entamer la lecture de cet article : il est technique, certes, mais ne concerne pas spécifiquement ni symfony ni doctrine. Le sujet de l'article est un problème technique très concret, qui se retrouve dans tous les langages, et avec n'importe quelle base de données.

En lisant le livre "Pratical symfony", plus spécifiquement le chapitre 6, je suis tombé sur un cas très classique : afficher une liste d'élément par catégorie, comme dans l'exemple qui suit.

L'approche ici - mais j'ai déjà vu ça ailleurs très souvent - est de récupérer la liste des catégories, puis, pour chaque catégorie, de récupérer la liste des offres de ladite catégorie.

Sauf qu'il y a un problème : voyez-vous lequel ?