Les Crash-Devs d'un Passionné

Faciliter la lecture de parametrize dans pytest

/Catégorie/python

Temps de lecture : 2 minutes

Lorsque l'on a besoin de tester une fonction il est intéressant de le faire sur une multitude de valeurs gérant les cas nominaux, aux limites et d'erreurs.
Pour cela nous allons garder la même fonction de test

clipboard
Copier le code
1
2
3
4
5
def test_user__check_attributs(id,name,birthday,country):
 assert type(id) is int 
 assert type(name) is str 
 assert type(datetime.strptime(birthday, "%d/%m/%Y")) is datetime 
 assert type(country) is str

Mais le jeu de données sera différent. Partons du principe que l'on veut avoir cet objet

clipboard
Copier le code
1
2
3
4
5
6
@dataclass
class User:
    id: int
    name: str 
    birthday: datetime
    country: str

Et avec la fonction pour tester son contenu

clipboard
Copier le code
1
2
3
4
5
6
@pytest.mark.parametrize("id,name,birthday,country", [(28, "Katnis", "14/10/1987", "France")])
def test_user__check_attributs(id,name,birthday,country):
 assert type(id) is int 
 assert type(name) is str 
 assert type(datetime.strptime(birthday, "%d/%m/%Y")) is datetime 
 assert type(country) is str

La fonction

parametrize
est surtout utile quand on veut tester plusieurs occurrences.

Elle prend une liste (plus précisément un

tuple)
des valeurs à injecter dans les paramètres de la fonction. Il faut donc exactement le même nombre de valeurs que de paramètres, ici 4

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@pytest.mark.parametrize("id,name,birthday,country", [
      (28, "Katnis", "14/10/1987", "France"),
      (12, "Emy", "12/12/2007", "France"),
      (46, "Paul", "10/02/1987", "France"),
      (100, "Fred", "09/12/1995", "France"),
      (3, "Henri", "12/07/1957", "France"),
])
def test_user__check_attributs(id,name,birthday,country):
 assert type(id) is int 
 assert type(name) is str 
 assert type(datetime.strptime(birthday, "%d/%m/%Y")) is datetime 
 assert type(country) is str

Vous imaginez que notre objet

User
peut être bien plus complexe et on pourrait imaginer encore plus de données à tester (dans le
parametrize
). Et ainsi le linter va nous introduire des retours à la ligne (lorsque notre instruction va dépasser la limite de caractère sur une ligne) et rendre moins lisible notre code.
Dans ce cas ce décorateur pollue visuellement le contenu de notre fonction de test

L'idée est de déporter ce jeu de donnée dans une constante

clipboard
Copier le code
1
2
3
4
5
6
7
USERS = [
    User(28, "Katnis", "14/10/1987", "France"),
    User(12, "Emy", "12/12/2007", "France"),
    User(46, "Paul", "10/02/1987", "France"),
    User(100, "Fred", "09/12/1995", "France"),
    User(3, "Henri", "12/07/1957", "France"),
]

On souhaite injecter ces données dans notre fonction parametrize de cette manière

clipboard
Copier le code
1
2
3
4
@pytest.mark.parametrize("id,name,birthday,country", [
       pytest.param(*current_user.__dict__.values())
       for current_user in USERS
])
  • On crée une compréhension de liste qui itère sur notre jeu de données
    USERS
  • On utilise la fonction
    pytest.param
    qui prend l'objet
    current_user
    en cours d'itération pour le convertir en dictionnaire (
    __dict__
    ) et lui extraire ces valeurs via
    values()
    (par défaut le parcours d'un dictionnaire retourne les clés). On fait du unpacking pour envoyer ces valeurs aux paramètres respectifs "id,name,birthday,country"

Ce qui donne au final

clipboard
Copier le code
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
USERS = [
 User(28, "Katnis", "14/10/1987", "France"),
 User(12, "Emy", "12/12/2007", "France"),
 User(46, "Paul", "10/02/1987", "France"),
 User(100, "Fred", "09/12/1995", "France"),
 User(3, "Henri", "12/07/1957", "France"),
]

@pytest.mark.parametrize("id,name,birthday,country", [
 pytest.param(*current_user.__dict__.values())
 for current_user in USERS
])
def test_user__check_attributs(id,name,birthday,country):
 assert type(id) is int
 assert type(name) is str
 assert type(datetime.strptime(birthday, "%d/%m/%Y")) is datetime
 assert type(country) is str

Ainsi la fonction de test

test_user__check_attributs
est beaucoup plus aérée et le jeu de données est détaché de la fonction