[PYTHON] La fonction contextlib peut être utile pour tester des processus qui utilisent des E / S standard telles que input ().

introduction

Il est difficile de tester le code dans lequel le traitement du système qui reçoit l'entrée de l'utilisateur comme ʻinput () `est écrit directement. Chaque fois que vous vérifiez que le test est difficile, vous devez le vérifier manuellement. Cela ne devrait pas être.

À propos des tests de traitement impliquant des entrées et des sorties

Il existe plusieurs façons d'activer les tests.

  1. Refonte sérieuse
  2. Utilisez unittest.mock
  3. Utilisez la fonction de vol d'entrée / sortie standard fournie par contextlib

Utilisez unittest.mock comme une méthode à usage général qui peut gérer diverses choses. Cependant, on dit parfois que la nécessité d'une simulation est une erreur de conception. Cela dit, vous pouvez utiliser des simulations si c'est un problème.

Je pense que vous pouvez trouver des histoires sur la refonte sérieuse à divers endroits. Omis cette fois.

Cette histoire est à peu près la troisième. Dans certains cas, il est plus facile d'utiliser les fonctions contextlib que de patcher le chemin en détail avec mock.

Voler la sortie standard

La fonction qui vole la sortie standard est fournie par défaut contextlib.redirect_stdout. (De même, il existe également une fonction recirect_stderr qui vole la sortie d'erreur standard)

Par exemple, vous pouvez facilement écrire un test de la partie sortie du processus dans laquelle print () est explicitement utilisé en utilisant StringIO.

import unittest
import contextlib


def foo():
    print("foo")


class InputTests(unittest.TestCase):
    def _calFUT(self):
        return foo()

    def test_it(self):
        from io import StringIO
        buf = StringIO()

        with contextlib.redirect_stdout(buf):
            self._calFUT()
        actual = buf.getvalue()
        self.assertEqual(actual, "foo\n")

Voler l'entrée standard

La fonction elle-même qui vole l'entrée standard n'est pas fournie (bien que ce soit une méthode irrégulière après tout). Cependant, il est étonnamment facile à implémenter en regardant l'implémentation telle que contextlib.redirect_stdout.

En fait, l'implémentation de contextlib.redirect_stdout etc. est la suivante.

class redirect_stdout(_RedirectStream):
    _stream = "stdout"

contextlib._RedirectStream échange les attributs du flux spécifié avant et après avec (avec __enter__ et __exit__). Dans l'exemple ci-dessus, sys.stdout est remplacé. En utilisant ceci, redirect_stdin qui vole l'entrée standard peut être facilement implémenté. Cependant, comme le nom commence par _, il s'agit d'un objet privé, nous ne pouvons donc pas garantir que cette implémentation fonctionnera à l'avenir.

Par exemple, supposons que vous ayez une fonction get_package_name () qui obtient le nom du package.

def get_package_name():
    package = input("input package name:")
    return {"package": package}

Bien sûr, les fonctions répertoriées ci-dessus sont des exemples de mauvaises définitions de fonction qui ne sont pas prises en compte pour les tests. Le test pour cela peut être écrit comme suit. Probablement plus facile que de se moquer si vous écrivez le test tout en le laissant tel quel.

import unittest
import contextlib


class redirect_stdin(contextlib._RedirectStream):
    _stream = "stdin"


class InputTests(unittest.TestCase):
    def _calFUT(self):
        return get_package_name()

    def test_it(self):
        from io import StringIO
        buf = StringIO()
        buf.write("hello\n")
        buf.seek(0)

        with redirect_stdin(buf):
            actual = self._calFUT()

        expected = {"package": "hello"}
        self.assertEqual(actual, expected)

prime

Vous pouvez également écrire un tel processus comme exemple du processus en utilisant contextlib.redirect_stdout. Cela peut être pratique lorsque vous souhaitez préparer un environnement avec une indentation ajoutée au résultat de sortie normal.

print("a")
with indent(2):
    print("b")
    with indent(2):
        print("c")
    print("d")
print("e")

Cela produira la sortie suivante.

a
  b
    c
  d
e

La mise en œuvre est la suivante.

import sys
import contextlib
from io import StringIO


@contextlib.contextmanager
def indent(n):
    buf = StringIO()
    with contextlib.redirect_stdout(buf):
        yield buf
    buf.seek(0)

    prefix = " " * n
    write = sys.stdout.write
    for line in buf:
        write(prefix)
        write(line)
    sys.stdout.flush()

Recommended Posts

La fonction contextlib peut être utile pour tester des processus qui utilisent des E / S standard telles que input ().
Une collection de ressources qui peuvent être utiles pour créer et développer des fichiers dotfiles