IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Python Discussion :

RESOLU : Tester qu'une exception est bien levée


Sujet :

Python

  1. #1
    Membre Expert
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    1 603
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 603
    Par défaut RESOLU : Tester qu'une exception est bien levée
    Bonjour,

    dans le cadre d'un projet professionnel, je dois ici déplacer un fichier d'un endroit vers un autre.

    J'utilise pytest pour les assert et pytest-mock si besoin dans mes tests.

    Voici le code de la méthode de la classe traitant de ce déplacement de fichier :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    from pathlib import Path
    from shutil import move
     
    class FileManagerError(Exception):
        """File error class."""
     
     
    class FileManager:
        def rename_PDF_file(self, sourcefile: Path, destfile: Path) -> None:
     
            try:
                move(sourcefile, destfile)
            except (FileNotFoundError, PermissionError) as err:
                raise FileManagerError(
                    f"ERREUR de renommage du fichier {sourcefile.name} "
                    + f"en {destfile.name} : {err}"
                ) from err
    Le test s'assurant que le déplacement du fichier est ok fonctionne et passe au vert dans Pytest.

    En revanche, celui levant l'exception FileManagerError ne fonctionne pas et ne lève pas l'exception.

    Voici le code du test concerné :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from pathlib import Path
    import pytest
    from pytest_mock import MockerFixture
    import shutil
    from file_manager import FileManager, FileManagerError
     
    def test_rename_PDF_file_is_not_ok(
            self, tmp_path: Path, mocker: MockerFixture
        ) -> None:
            sourcefile: Path = tmp_path / "in.pdf"
            sourcefile.write_bytes(b"foo bar")
            destfile: Path = tmp_path / "out.pdf"
            mocker.patch.object(shutil, "move", side_effect=PermissionError)
            manager: FileManager = FileManager(tmp_path, tmp_path)
            with pytest.raises(FileManagerError):
                manager.rename_PDF_file(sourcefile=sourcefile, destfile=destfile)
    L'erreur est :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    Failed: DID NOT RAISE <class 'file_manager.FileManagerError'>
    J'ai patché shutil.move avec différents effets de bord, comme IOError, FileNotFoundError et ici PermissionError, même constat.
    J'ai essayé mocker.patch('shutil.move', ...) à la place de mocker.patch.object(shutil, 'move', ...), même résultat.

    J'ai aussi essayé ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    def test_rename_PDF_file_is_not_ok(
            self, tmp_path: Path, mocker: MockerFixture
        ) -> None:
            def shutil_patch(*args: Any, **kwargs: Any) -> None:
                raise PermissionError
            sourcefile: Path = tmp_path / "in.pdf"
            sourcefile.write_bytes(b"foo bar")
            destfile: Path = tmp_path / "out.pdf"
            mocker.patch.object(shutil, "move", shutil_patch)
            manager: FileManager = FileManager(tmp_path, tmp_path)
            with pytest.raises(FileManagerError):
                manager.rename_PDF_file(sourcefile=sourcefile, destfile=destfile)
    Et pareil.

    Je sais que je peux inventer un répertoire bidon pour la destination du fichier afin que l'exception FileNotFoundError soit levée et que mon test passe au vert.

    Mais bof quoi. Je préfère mocker shutil.move.

    Auriez-vous une idée ?

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 718
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 718
    Par défaut
    Salut,

    Ce que vous racontez semble dire que le mock ne marche pas.

    Je ne serais pas surpris que ça tombe en marche en remplaçant le "from shutil import move" par un "import shutil" et l'utilisation explicite de shutil.move.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Expert confirmé Avatar de papajoker
    Homme Profil pro
    Développeur Web
    Inscrit en
    Septembre 2013
    Messages
    2 322
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Nièvre (Bourgogne)

    Informations professionnelles :
    Activité : Développeur Web
    Secteur : High Tech - Multimédia et Internet

    Informations forums :
    Inscription : Septembre 2013
    Messages : 2 322
    Par défaut
    bonjour

    petit cas particulier
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    rename_PDF_file(Path("/tmp/1.pdf"), Path("/tmp/"))
    ici, j'intercepterais OSError dans cette fonction pour couvrir plus que les 2 exceptions classiques

    EDIT: de même , juste un problème d'importation de shutil

  4. #4
    Membre Expert
    Avatar de Pyramidev
    Homme Profil pro
    Tech Lead
    Inscrit en
    Avril 2016
    Messages
    1 513
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Tech Lead

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 513
    Par défaut
    Bonjour,

    Dans file_manager.py, après avoir fait from shutil import move, la variable file_manager.move pointe vers le même objet que shutil.move. Appelons "obj1" cet objet. Rq : dans le cas présent, cet objet est une fonction.

    Dans le fichier de test, je pense que l'expression mocker.patch.object(shutil, "move", side_effect=PermissionError) modifie shutil pour que sa propriété move ne pointe plus vers "obj1", mais vers un nouvel objet "obj2" qui, quand on l'appelle comme une fonction, lance une exception.

    Mais file_manager.FileManager.rename_PDF_file continue d'appeler file_manager.move qui pointe toujours vers l'objet "obj1" de départ.

    Je fais le même pari que wiztricks :

    Citation Envoyé par wiztricks Voir le message
    Je ne serais pas surpris que ça tombe en marche en remplaçant le "from shutil import move" par un "import shutil" et l'utilisation explicite de shutil.move.
    Dans file_manager.py, après avoir fait import shutil, la variable file_manager.shutil pointera vers le même objet que shutil. Appelons "obj3" cet objet. Rq : dans le cas présent, cet objet est un module.

    Dans le fichier de test, l'expression mocker.patch.object(shutil, "move", side_effect=PermissionError) modifiera alors l'objet pointé par shutil, qui est bien "obj3". La propriété move de cet objet "obj3" ne pointera plus vers "obj1", mais vers "obj2".

    À part ça, personnellement, pour faire des tests unitaires par dessus du code qui manipulait le système de fichiers, j'utilisais pyfakefs. C'était très pratique.

    Documentation : http://jmcgeheeiv.github.io/pyfakefs/master/usage.html

  5. #5
    Membre Expert
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    1 603
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 603
    Par défaut
    Merci à tous pour vos interventions et pour m'aider à résoudre mon (petit) soucis.

    C'est effectivement bien l'emploi de en lieu et place de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    from shutil import move
    dans ma classe qui a résolu mon problème.

    Python regorge de petites subtilités comme celle-ci et je me fais régulièrement berner

    Merci à vous !

  6. #6
    Membre Expert
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    1 603
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 603
    Par défaut
    Citation Envoyé par Pyramidev Voir le message
    Bonjour,

    Dans file_manager.py, après avoir fait from shutil import move, la variable file_manager.move pointe vers le même objet que shutil.move. Appelons "obj1" cet objet. Rq : dans le cas présent, cet objet est une fonction.

    Dans le fichier de test, je pense que l'expression mocker.patch.object(shutil, "move", side_effect=PermissionError) modifie shutil pour que sa propriété move ne pointe plus vers "obj1", mais vers un nouvel objet "obj2" qui, quand on l'appelle comme une fonction, lance une exception.

    Mais file_manager.FileManager.rename_PDF_file continue d'appeler file_manager.move qui pointe toujours vers l'objet "obj1" de départ.
    Merci pour l'explication qui clarifie bien pourquoi mon test plantait !

    J'ai tendance à n'importer des libs que j'utilise que les méthodes qui m'intéressent mais je vois bien l'effet de bord que cela peut engendrer.

    Citation Envoyé par Pyramidev Voir le message
    À part ça, personnellement, pour faire des tests unitaires par dessus du code qui manipulait le système de fichiers, j'utilisais pyfakefs. C'était très pratique.
    Merci pour l'idée mais au taf, nous répondons à des règles d'usage strictes : respect au maximum de la PEP8, usage de linters, emploi de pathlib pour toutes les manipulations de chemins/fichiers... Et pytest + pytest-django (si besoin) + pytest-mock pour couvrir nos tests

  7. #7
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 718
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 718
    Par défaut
    Salut,

    Citation Envoyé par Arioch Voir le message
    Python regorge de petites subtilités comme celle-ci et je me fais régulièrement berner
    Ce qui est "subtil" ici est que ça se ramène à assigner 2 références à un même objet comme:
    pour se demander pourquoi lorsqu'on assigne un autre objet à B, on accède toujours à l'ancien objet via A.

    Bien sur, ce "détail" enfoui dans la complexité des imports, la notion de module, la magie des mocks,... se fait oublier... mais from shutil move (le A) crée une référence à un objet alors que shutil.move en sera une autre et l'assignation faite à l'un casse la cohérence: c'est le même Python et la même punition.

    Une "solution" pourrait être de laisser le module file_manager tel qu'il est écrit.... et de faire un import file_manager dans le script de test pour "mocker" file_manager.move.

    Personnellement, je préfère éviter les from module import X, Y, Z car ça n'aide pas à savoir d'où sort la fonction appelée (à la relecture). Quand je le fais, c'est pour "optimiser" une fonction qui appellera N fois module.X. Dans ce cas, j'inclus un from module import X au début de la fonction - ce qui me permet de me rappeler de l'intention initiale -.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre Expert
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2003
    Messages
    1 603
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Industrie

    Informations forums :
    Inscription : Février 2003
    Messages : 1 603
    Par défaut
    Citation Envoyé par wiztricks Voir le message
    Personnellement, je préfère éviter les from module import X, Y, Z car ça n'aide pas à savoir d'où sort la fonction appelée (à la relecture). Quand je le fais, c'est pour "optimiser" une fonction qui appellera N fois module.X. Dans ce cas, j'inclus un from module import X au début de la fonction - ce qui me permet de me rappeler de l'intention initiale -.

    - W
    Je vais opter pour cette manière de faire désormais. Ca m'évitera de me faire des nœuds au cerveau inutilement

Discussions similaires

  1. Réponses: 0
    Dernier message: 16/10/2012, 13h39
  2. [WD15] Tester si un paramètre est bien renvoyé par une fenêtre
    Par tux59 dans le forum WinDev
    Réponses: 3
    Dernier message: 29/03/2010, 12h35
  3. [VS2005] S'arreter quand une exception est levée
    Par ZePostman dans le forum Visual Studio
    Réponses: 8
    Dernier message: 28/08/2008, 15h24
  4. Réponses: 2
    Dernier message: 26/04/2008, 17h31
  5. Comment tester qu'une base est bien présente?
    Par xilay dans le forum MS SQL Server
    Réponses: 2
    Dernier message: 20/10/2005, 16h26

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo