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.
Il existe plusieurs façons d'activer les tests.
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.
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")
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)
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()