Bienvenue sur IndexError.

Ici vous pouvez poser des questions sur Python et le Framework Django.

Consultez la FAQ pour améliorer vos chances d'avoir des réponses à vos questions.

unit-testing: fonctions sans retour

+4 votes

Est-ce qu' "il faut" tester les fonctions qui ne fournissent pas de retour, comme une fonction d'affichage par exemple? si oui, comment?

demandé 24-Aou-2015 par furankun (1,434 points)

1 Réponse

+5 votes
 
Meilleure réponse

Tu peux. Tout dépend de si la fonction est importante ou pas.

Pour le tester une solution "simple" est de faire un monkey-patch de sys.stdout.

Petite démonstration :

import sys
from io import StringIO

# On mémorise l'ancienne sortie standard avant
old_stdout = sys.stdout
# On remplace ça par un flux de chaine en mémoire
result_stream = StringIO()
sys.stdout = result_stream

# On fait un print => rien ne va s'afficher à l'écran
print(1,2,3)

# On remet l'ancien flux
sys.stdout = old_stdout

# On regarde le résultat
assert result_stream.getvalue() == '1 2 3\n'

Dans la pratique on utilisera plutôt des mocks pour faire ça proprement.

On peut aussi utiliser le context manager redirect_stdout dans contextlib qui va faire ça pour toi. L'exemple precedent devient :

from contextlib import redirect_stdout
from io import StringIO

result_stream = io.StringIO()
with redirect_stdout(result_stream):
    print(1,2,3)

assert result_stream.getvalue() == '1 2 3\n'
répondu 24-Aou-2015 par Kje (464 points)
sélectionné 25-Aou-2015 par furankun

parfait ce contexte manager (python 3 only visiblement)

Il est pas très dur a faire en Python 2. Voici son code (il fait la même chose que mon premier exemple, en un peu mieux car il est ré-entrant) :

import sys

class redirect_stdout:
    def __init__(self, new_target):
        self._new_target = new_target
        # We use a list of old targets to make this CM re-entrant
        self._old_targets = []

    def __enter__(self):
        self._old_targets.append(sys.stdout)
        sys.stdout = self._new_target
        return self._new_target

    def __exit__(self, exctype, excinst, exctb):
        sys.stdout = self._old_targets.pop()

On en revient aux mocks... va vraiment falloir que je comprenne comment ça marche à la fin, et surtout où placer le curseur entre test sur fichier réel et test sur mock (voir mon autre question sur qu'est-ce qu'on mocke).

En l'occurrence ici pas besoin de mock. Le contexte manager est plus simple et plus explicite

Le problème c'est que je ne fais pas que du stdout, je génère des figures maplotlib.

Il est très difficile voir impossible de tester la génération de figure. Le mieux que tu puisse faire c'est testé que de bonnes valeurs soient fournient.

Mais les graph sont rarement un truc testé

Le mieux que tu puisse faire c'est testé que de bonnes valeurs soient fournient.

C'est ce que j'ai fait oui, et là je comprends le principe de mocker l'appel d'une fonction. Si jamais je trouve comment faire pour les figures je viendrai poster en commentaire ici.

Bon j'ai simplement appelé la fonction telle quelle, après lui avoir ajouté un flag pour affichage ou non. Donc je peux tester toutes les conditions, avec des images générées aléatoirement, en affichant ou non.

...