Bienvenue sur IndexError.

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

Mais aussi sur les technos front comme React, Angular, Typescript et Javascript en général.

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

Mock de la fonction random.choice pour retourner selon un hasard prédéterminé

+3 votes

Une partie du code que je souhaite tester utilise du hasard, plus précisément la fonction random.choice. Un exemple rapide:

import random

DIRECTION_STRAIGHT_ON = 2
DIRECTION_TURN_LEFT = 1
DIRECTION_TURN_RIGTH = 3

def get_next_direction():
    return random.choice([DIRECTION_STRAIGHT_ON, 
                          DIRECTION_TURN_LEFT, 
                          DIRECTION_TURN_RIGTH])

Cependant je souhaite que lors de mes tests, random.choice retourne ses valeurs de manière régulière. C'est à dire que, pour une liste X donné à random.choice les résultats soient sortis dans le même ordre a chaque instance python.

Pour illustrer, le résultat se voudrais d'être du genre:

# python
Python 3.4.0 (default, Jun 19 2015, 14:18:46) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import myfakerandom
>>> myfakerandom.choice([2, 3, 4, 5])
3
>>> myfakerandom.choice([2, 3, 4, 5])
5
>>> myfakerandom.choice([2, 3, 4, 5])
2
>>> myfakerandom.choice([2, 3, 4, 5])
5
>>> myfakerandom.choice([2, 3, 4, 5])
3
>>> # etc ... un hasard à l’œil humain
...
>>> myfakerandom.choice([3, 4, 5])
4
>>> myfakerandom.choice([3, 4, 5])
4
>>> myfakerandom.choice([3, 4, 5])
5
>>> myfakerandom.choice([3, 4, 5])
3
>>> # etc ... un hasard à l’œil humain
...
exit()
# python
Python 3.4.0 (default, Jun 19 2015, 14:18:46) 
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import myfakerandom
>>> myfakerandom.choice([2, 3, 4, 5])
3
>>> myfakerandom.choice([2, 3, 4, 5])
5
>>> myfakerandom.choice([2, 3, 4, 5])
2
>>> myfakerandom.choice([2, 3, 4, 5])
5
>>> myfakerandom.choice([2, 3, 4, 5])
3
>>> # etc ... un hasard à l’œil humain, mais les sorties se font dans le même ordre que lors de l'autre instance python
...
>>> myfakerandom.choice([3, 4, 5])
4
>>> myfakerandom.choice([3, 4, 5])
4
>>> myfakerandom.choice([3, 4, 5])
5
>>> myfakerandom.choice([3, 4, 5])
3
>>> # etc ... un hasard à l’œil humain, mais les sorties se font dans le même ordre que lors de l'autre instance python
exit()
demandé 5-Aou-2015 par bux (130 points)

2 Réponses

+4 votes
 
Meilleure réponse

Pas besoin de mock, c'est tout l'objet de la fonction seed. Au début de ton test écrit quelque chose comme :

import random
# ....
# Dans ta fonction de test :
random.seed(42)

et ainsi à chaque lancement du test il y aura la même séquence aléatoire qui sera générée.

Démonstration :

>>> import random
>>> random.seed(42)
>>> random.choice([2, 3, 4, 5])
2
>>> random.choice([2, 3, 4, 5])
2
>>> random.choice([2, 3, 4, 5])
4
>>> random.choice([2, 3, 4, 5])
3
>>> random.choice([2, 3, 4, 5])
3
>>> # on ré-initialise la graine
... random.seed(42)
>>> random.choice([2, 3, 4, 5])
2
>>> random.choice([2, 3, 4, 5])
2
>>> random.choice([2, 3, 4, 5])
4
>>> random.choice([2, 3, 4, 5])
3
>>> random.choice([2, 3, 4, 5])
3
répondu 5-Aou-2015 par Kje (464 points)
sélectionné 5-Aou-2015 par bux

Je serais toujours surpris par ce merveilleux langage, où les comportements que l'on souhaitera utiliser existent déjà. Merci !

En l’occurrence tout langage qui se respecte (c'est à dire 99% des langages, mais pas JavaScript) exposent cette fonction qui est issue de l'implémentation C sous-jacente. Et effectivement pour les tests c'est une bonne pratique de fixer la graine pour éviter des tests qui planteraient de manière irrégulières.

Par défaut Python appel lui aussi cette fonction mais avec l'heure courante pour générer des suites différentes à chaque fois.

Merci pour cette précision ;)

+1 vote

En complément de la réponse avec le paramétrage du seed :

Monkeypatch avec le module mock :

In [1]: import mock
In [2]: import random
In [3]: random.choice = mock.Mock(return_value=10)
In [4]: random.choice([1,2,3])
Out[4]: 10
In [6]: random.choice([4,5,7])
Out[6]: 10

Une manière plus propre pour utiliser mock avec un context manager :

In [10]: with mock.patch.object(random, 'choice', return_value=2.5) as choice:
    print choice([3,4,2])
2.5

Monkeypatch un peu sale avec une simple lambda :

In [7]: random.choice = lambda x : 5
In [8]: random.choice([4,5,7])
Out[8]: 5

La seconde solution est pratique, elle permet de gérer finement le scope dans lequel la méthode est mockée.

répondu 5-Aou-2015 par jc (2,674 points)
...