2 votes

Mocking os.environ avec python unittests

J'essaie de tester une classe qui gère pour moi le répertoire de travail en fonction d'un paramètre donné. Pour ce faire, nous utilisons une variable de classe pour les mettre en correspondance.

Lorsqu'une valeur spécifique est transmise, le chemin d'accès est récupéré à partir des variables d'environnement (voir baz dans l'exemple ci-dessous). C'est le cas spécifique que j'essaie de tester.

J'utilise Python 3.8.13 et unittest .

J'essaie de l'éviter :

  • Je ne veux pas me moquer de la WorkingDirectory.map car je veux m'assurer que nous récupérons les données du dictionnaire environ avec cette variable particulière ( BAZ_PATH ).
  • A moins que ce ne soit la seule solution, je voudrais éviter de modifier les valeurs pendant le test, c'est-à-dire que je préférerais ne pas faire quelque chose comme : os.environ["baz"] = DUMMY_BAZ_PATH

Ce que j'ai essayé

J'ai essayé de simuler le environ en tant que dictionnaire, comme le suggèrent d'autres publications, mais je n'arrive pas à le faire fonctionner pour une raison quelconque.

# working_directory.py
import os

class WorkingDirectory:
    map = {
        "foo": "path/to/foo",
        "bar": "path/to/bar",
        "baz": os.environ.get("BAZ_PATH"),
    }

    def __init__(self, env: str):
        self.env = env
        self.path = self.map[self.env]

    @property
    def data_dir(self):
        return os.path.join(self.path, "data")

    # Other similar methods...

Fichier de test :

# test.py

import os
import unittest

from unittest import mock

from working_directory import WorkingDirectory

DUMMY_BAZ_PATH = "path/to/baz"

class TestWorkingDirectory(unittest.TestCase):
    @mock.patch.dict(os.environ, {"BAZ_PATH": DUMMY_BAZ_PATH})
    def test_controlled_baz(self):
        wd = WorkingDirectory("baz")
        self.assertEqual(wd.path, DUMMY_BAZ_PATH)

Erreur

Comme le montre l'erreur, os.environ ne semble pas être correctement patché puisqu'il renvoie Null .

======================================================================
FAIL: test_controlled_baz (test_directory_structure_utils.TestWorkingDirectory)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "~/.pyenv/versions/3.8.13/lib/python3.8/unittest/mock.py", line 1756, in _inner
    return f(*args, **kw)
  File "~/Projects/dummy_project/tests/unit/test_directory_structure_utils.py", line 127, in test_controlled_baz
    self.assertEqual(wd.path, DUMMY_BAZ_PATH)
AssertionError: None != 'path/to/baz'

----------------------------------------------------------------------
Ran 136 tests in 0.325s

FAILED (failures=1, skipped=5)

Cela semble s'expliquer par le fait que le BAZ_PATH n'existe pas en réalité. Cependant, je m'attendrais à ce que ce soit OK puisqu'il s'agit d'un correctif.

Quand, dans le dictionnaire de la cartographie, "baz": os.environ.get("BAZ_PATH") Je réplique BAZ_PATH pour une variable qui existe réellement dans mon environnement, c'est-à-dire HOME il renvoie la valeur réelle de HOME au lieu de la DUMMY_BAZ_PATH , ce qui m'a amené à penser que je faisais certainement quelque chose de mal Parcheando.

AssertionError: '/Users/cestla' != 'path/to/baz'

Résultat attendu

Il est évident que j'attends le test_controlled_baz passe avec succès.

5voto

Nimrod Shanny Points 469

Le problème est donc que vous avez ajouté map en tant que variable statique. Votre patch fonctionne correctement comme vous pouvez le voir ici :

le correctif fonctionne réellement

Le problème est que lorsqu'il s'exécute, il est déjà trop tard car la variable map a déjà été calculée (avant le patch). Si vous le souhaitez, vous pouvez la déplacer dans la section init et il fonctionnera correctement :

class WorkingDirectory:

def __init__(self, env: str):
    self.map = {
        "foo": "path/to/foo",
        "bar": "path/to/bar",
        "baz": os.environ.get("BAZ_PATH")
    }
    self.env = env
    self.path = self.map[self.env]

Si, pour une raison ou une autre, vous souhaitez qu'il reste statique, vous devez également patcher l'objet lui-même. écrire quelque chose comme ceci fera l'affaire :

class TestWorkingDirectory(unittest.TestCase):
@mock.patch.dict(os.environ, {"BAZ_PATH": DUMMY_BAZ_PATH})
def test_controlled_baz(self):
    with mock.patch.object(WorkingDirectory, "map", {
        "foo": "path/to/foo",
        "bar": "path/to/bar",
        "baz": os.environ.get("BAZ_PATH")
    }):
        wd = WorkingDirectory("baz")
        self.assertEqual(wd.path, DUMMY_BAZ_PATH)

0voto

Gameplay Points 146

Ce n'est pas une réponse directe à votre question, mais c'est une réponse valable dans les deux cas : N'essayez pas de patcher cela (c'est possible, mais plus difficile et encombrant). Utilisez le fichier de configuration de votre projet.

par exemple, utiliser pyproject.toml et à l'intérieur configurer l'extension pytest :

[tool.pytest.ini_options]
env=[
"SOME_VAR_FOR_TESTS=some_value_for_that_var"
]

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X