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.

Pytest: vérifier qu'une fonction ne lève pas d'exception

+2 votes

Je cherche a développer une petite fonction pour évaluer dans pytest si ma fonction a tester lève une exception.
Si je veux vérifier que l'exception est levee, je fais:

def test_foo_raise():
    with pytest.raises(MonException) as exception:
        foo(param)
    assert exception.value.message == MonException.MSG

Par contre, comment vérifier que foo ne lève pas l'exception ?
Je peux faire:

def test_foo_no_raise():
    foo(param)

Si une exception est levée, pytest sort avec une exception. Mais dans ce cas, on n'assert rien et je préfèrerai qu'un assert trace l'erreur (je génère des fichiers de logs en sortie avec mes assert échoué)

J'ai pensé développer une petite fonction style:

def no_raise(call):
    result = True
    try:
        call()
    except as exception:
        result = False
    return result

def test_foo_no_raise():
    assert no_raise(foo(param))

mais ça ne peut pas marcher car le paramètre call de noraise est évalué avant l'appel de noraise. Il faudrait que call ne soit évalué que dans le body de no_raise.

Une solution ?

demandé 25-Aou-2016 par toub (408 points)
reclassée 30-Aou-2016 par max

1 Réponse

+2 votes
 
Meilleure réponse

Pour reprendre cette réponse de SO, tu devrais juste appeler la fonction. Pytest sait comment la gérer : c'est son job !

def test_foo():
    foo(param)  # si jamais ça lève une exception, pytest le fera savoir par le biais habituel

Autre solution, plus verbeuse mais plus explicite :

def test_foo():
    try:
        foo(param)
    except:
        pytest.fail("Unexpected Error ..")

Je conseille la première méthode : la seconde cache la véritable nature d'une exception levée par la fonction, en levant une exception générique. C'est un peu con pour un test unitaire qui est sensé rapporter les problèmes rencontrés.
Et la première est beaucoup plus claire. Éventuellement, on peut y coller un commentaire pour expliquer que c'est juste pour vérifier que la fonction ne lève pas d'exception.

Concernant ton logging, celui-ci devrais certainement pouvoir gérer les ValueError comme il gère les AssertError, non ?
Voici des pistes pour utiliser pytest en modifiant son comportement.


Pour l'exercice, voilà un test qui lève une exception:

def test_foo():
    raise ValueError

Et voici la sortie de pytest:

==== FAILURES ====
____ test_ie ____

    def test_ie():

>       raise ValueError
E       ValueError

test_foo.py:2: ValueError
répondu 25-Aou-2016 par lucas (2,292 points)
sélectionné 30-Aou-2016 par toub

merci pour ta réponse,

effectivement j'avais envisagé la 1ère solution, reste le problème du logging. Je devrais pouvoir m'en sortir via les hook pytest mais ca prend un peu de temps. Entre temps j'ai développé ça:

class UnexpectedException():
  def __init__(self, exception):
      self.exception = exception
  def __nonzero__(self):
      return False
  def __repr__(self):
      return repr(self.exception)

def no_raise(fn, *args):
  result = True
  try:
    fn(*args)
  except Exception as exception:
    result = UnexpectedException(exception)
  return result

def foo(param):
  raise ValueError("foo error")

def test():
assert no_raise(foo, 1)

Voici les retours py.test:

    def test():
>     assert no_raise(foo, 1)
E     assert ValueError('foo error',)
E      +  where ValueError('foo error',) = no_raise(foo, 1)

Ainsi on récupère bien le type de l'erreur dans le assert. Que penses tu de cette solution et de sa cohérence avec la philosophie de pytest ?

Si elle fait ce dont tu as besoin, c'est déjà ça.

Pour la philosophie de pytest, je pense que le mieux est de passer par son interface interne pour redéfinir les comportements en cas de fail (et donc, dans ton cas, insérer du logging).
À ta place, je mettrais le paquet sur pytest, justement pour m'éviter d'avoir à mettre en place ce genre de hack que j'aurais du mal à comprendre le jour où il faudra le remplacer. (et ce sera pire si c'est quelqu'un d'autre qui s'en charge)
En parcourant la doc, il semblerait que la meilleure façon de faire soit de passer par des hooks. C'est ce que font les plugins qui étendent le comportement de pytest. Or, comme tu cherche à étendre son comportement, la solution toute prête est de faire un plugin.

Il pourrait exister des plugins, voir des systèmes de tests complet, avec logging intégré, comme celui-ci. (glané en cherchant pytest log) sur pypi.

Une autre solution serait de passer par unittest, dont l'interface interne me semble beaucoup plus documentée.

Enfin, la solution la plus simple serait certainement ça:

pytest . > logs

Avec ça, la sortie de pytest va directement dans le fichier logs. Ça fait des logs.

Après réflexion et quelques essais complémentaires, je me rends à tes arguments, je vais me contenter d'exécuter mon code sans checker qu'aucune exception n'est levée, ça me parait effectivement inutile. Merci pour ton aide

...