[PYTHON] Die contextlib-Funktion kann nützlich sein, um Prozesse zu testen, die Standard-E / A verwenden, z. B. input ().

Einführung

Es ist schwierig, den Code zu testen, in dem die Verarbeitung des Systems, das die Benutzereingaben wie "input ()" empfängt, direkt geschrieben wird. Jedes Mal, wenn Sie überprüfen, ob der Test schwierig ist, müssen Sie ihn manuell überprüfen. Das sollte nicht sein.

Informationen zum Testen der Verarbeitung mit Eingabe und Ausgabe

Es gibt verschiedene Möglichkeiten, das Testen zu aktivieren.

  1. Ernsthaft neu gestalten
  2. Verwenden Sie unittest.mock
  3. Verwenden Sie die Standard-Ein- / Ausgabediebstahlfunktion von contextlib

Verwenden Sie unittest.mock als Allzweckmethode, die mit verschiedenen Dingen umgehen kann. Es wird jedoch manchmal gesagt, dass die Notwendigkeit eines Scheines ein Entwurfsfehler ist. Das heißt, Sie können Mock verwenden, wenn es ein Ärger ist.

Ich denke, man kann an verschiedenen Orten Geschichten über ernsthafte Neugestaltungen finden. Diesmal weggelassen.

Diese Geschichte handelt von der dritten. In einigen Fällen ist es einfacher, die contextlib-Funktionen zu verwenden, als den Pfad detailliert mit mock zu patchen.

Standardausgabe stehlen

Die Funktion, die die Standardausgabe stiehlt, ist contextlib.redirect_stdout. (In ähnlicher Weise gibt es auch eine Funktion "recirect_stderr", die die Standardfehlerausgabe stiehlt.)

Sie können beispielsweise einfach einen Test des Ausgabeteils des Prozesses schreiben, in dem print () explizit mit StringIO verwendet wird.

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")

Standardeingabe stehlen

Die Funktion selbst, die Standardeingaben stiehlt, wird nicht bereitgestellt (obwohl es sich immerhin um eine unregelmäßige Methode handelt). Die Implementierung ist jedoch überraschend einfach, wenn man sich die Implementierung wie "contextlib.redirect_stdout" ansieht.

Tatsächlich ist die Implementierung von "contextlib.redirect_stdout" usw. wie folgt.

class redirect_stdout(_RedirectStream):
    _stream = "stdout"

contextlib._RedirectStream tauscht die Attribute des angegebenen Streams vorher und nachher mit (mit __enter__ und __exit__) aus. Im obigen Beispiel wird sys.stdout ersetzt. Auf diese Weise kann redirect_stdin, das Standardeingaben stiehlt, einfach implementiert werden. Da der Name jedoch mit "_" beginnt, handelt es sich um ein privates Objekt, sodass wir nicht garantieren können, dass diese Implementierung in Zukunft funktioniert.

Angenommen, Sie haben eine Funktion "get_package_name ()", die den Paketnamen abruft.

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

Natürlich sind die oben aufgeführten Funktionen Beispiele für fehlerhafte Funktionsdefinitionen, die beim Testen nicht berücksichtigt werden. Der Test hierfür kann wie folgt geschrieben werden. Wahrscheinlich einfacher als zu verspotten, wenn Sie den Test schreiben, während Sie ihn so lassen, wie er ist.

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)

Bonus

Sie können einen solchen Prozess auch als Beispiel für den Prozess mit "contextlib.redirect_stdout" schreiben. Dies kann praktisch sein, wenn Sie eine Umgebung mit Einrückungen vorbereiten möchten, die zum normalen Ausgabeergebnis hinzugefügt werden.

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

Dies erzeugt die folgende Ausgabe.

a
  b
    c
  d
e

Die Implementierung ist wie folgt.

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

Die contextlib-Funktion kann nützlich sein, um Prozesse zu testen, die Standard-E / A verwenden, z. B. input ().
Eine Sammlung von Ressourcen, die zum Erstellen und Erweitern von Punktedateien hilfreich sein können