Wie können Eingaben von stdin, Dateien und Umgebungsvariablen an Python-Unit-Tests übergeben werden?




unit-testing user-input (4)

So schreiben Sie Tests, bei denen Bedingungen wie die folgenden auftreten:

  1. Benutzereingabe testen
  2. Testeingabe aus einer Datei gelesen.
  3. Testeingabe von einer Umgebungsvariablen gelesen.

Es wäre toll, wenn mir jemand zeigen könnte, wie ich mich den oben genannten Szenarien annähern kann; Es wäre immer noch toll, wenn du mich auf ein paar Dokumente / Artikel / Blogposts verweisen könntest, die ich lesen könnte.


Wenn Sie mit raw_input (oder einer anderen spezifischen Eingabequelle) verbunden sind, bin ich ein großer Befürworter der Mock-Bibliothek . Angesichts des Codes, den Mark Ruschakoff in seinem Beispiel verwendete:

def say_hello():
    name = raw_input("What is your name? ")
    return "Hello " + name

Ihr Testcode könnte mock verwenden:

import mock

def test_say_hello():
     with mock.patch('__builtin__.raw_input', return_value='dbw'):
         assert say_hello() == 'Hello dbw'

     with mock.patch('__builtin__.raw_input', side_effect=['dbw', 'uki']):
         assert say_hello() == 'Hello dbw'
         assert say_hello() == 'Hello uki'

Diese Behauptungen würden bestehen. Beachten Sie, dass side_effect die Elemente der Liste in der richtigen Reihenfolge zurückgibt. Es kann so viel mehr! Ich würde empfehlen, die Dokumentation zu überprüfen.


Wenn Sie ohne einen externen Prozess entkommen können, tun Sie dies.

Es gibt jedoch Situationen, in denen dies kompliziert ist und Sie wirklich einen Prozess verwenden möchten, z. B. wenn Sie die Befehlszeilenschnittstelle einer ausführbaren C-Datei testen möchten.

Benutzereingabe

Verwenden Sie subprocess.Popen wie in:

process = subprocess.Popen(
    command,
    shell  = False,
    stdin  = subprocess.PIPE,
    stdout = subprocess.PIPE,
    stderr = subprocess.PIPE,
    universal_newlines = True
)
stdout, stderr = process.communicate("the user input\nline 2")
exit_status = process.wait()

Es gibt keinen Unterschied zwischen der Eingabe von einem Benutzer und der Eingabe aus einer Pipe für Eingaben, die mit Methoden wie raw_input oder sys.stdin.read() .

Dateien

  • Erstellen Sie ein temporäres Verzeichnis und erstellen Sie die Dateien, von denen Sie in Ihren Test- setUp Methoden lesen setUp :

    tdir = tempfile.mkdtemp(
        prefix = 'filetest_', 
    )
    fpath = os.path.join(tdir,'filename')
    fp = open(fpath, 'w')
    fp.write("contents")
    fp.close()
    
  • Führen Sie das Lesen der Datei in den Tests durch.

  • Entfernen Sie anschließend das Temp-Verzeichnis.

    shutil.rmtree(tdir)
    
  • Es ist ziemlich kompliziert, aus Dateien zu lesen, und die meisten Programme können entweder aus Dateien oder aus STDIN lesen (zB mit fileinput ). Also, was Sie testen möchten, was passiert, wenn ein bestimmter Inhalt eingegeben wird und Ihr Programm STDIN akzeptiert, verwenden Sie einfach Popen , um das Programm zu testen.

Umgebungsvariablen

  • Setze die Umgebungsvariablen mit os.environ["THE_VAR"] = "the_val"
  • Lösche sie mit del os.environ["THE_VAR"]
  • os.environ = {'a':'b'} funktioniert nicht
  • Rufen Sie dann subprocess.Popen . Die Umgebung wird vom aufrufenden Prozess geerbt.

Vorlagencode

Ich habe ein Modul auf meinem GitHub , das STDOUT , STDERR und den STDIN , Befehlszeilenargumente und Umgebung STDIN . Überprüfen Sie auch die Tests für dieses Modul unter dem Befehl "Tests". Es muss viel bessere Module dafür geben, also nimm meine nur für Lernzwecke.


Pyty verwenden :

import os


def test_user_input(monkeypatch):
    inputs = [10, 'y']
    input_generator = (i for i in inputs)
    monkeypatch.setattr('__builtin__.raw_input', lambda prompt: next(input_generator))
    assert raw_input('how many?') == 10
    assert raw_input('you sure?') == 'y'


def test_file_input(tmpdir):
    fixture = tmpdir.join('fixture.txt')
    fixture.write(os.linesep.join(['1', '2', '3']))
    fixture_path = str(fixture.realpath())
    with open(fixture_path) as f:
        assert f.readline() == '1' + os.linesep


def test_environment_input(monkeypatch):
    monkeypatch.setenv('STAGING', 1)
    assert os.environ['STAGING'] == '1'

In allen drei Situationen, die Sie beschrieben haben, müssen Sie speziell darauf achten, dass Sie lockere Verbindungen in Ihrem Design verwenden.

Müssen Sie Pythons raw_input Methode wirklich testen? Die open Methode? os.environ.get ? Nein.

Sie müssen Ihr Design so einrichten, dass Sie andere Möglichkeiten zum Abrufen dieser Eingabe verwenden können. Dann werfen Sie während Ihrer raw_input einen Stub irgendeiner Art, der raw_input nicht raw_input oder nicht open .

Zum Beispiel könnte Ihr normaler Code etwas wie sein:

import os
def say_hello(input_func):
    name = input_func()
    return "Hello " + name

def prompt_for_name():
    return raw_input("What is your name? ")

print say_hello(prompt_for_name)
# Normally would pass in methods, but lambdas can be used for brevity
print say_hello(lambda: open("a.txt").readline())
print say_hello(lambda: os.environ.get("USER"))

Die Sitzung sieht folgendermaßen aus:

What is your name? somebody
Hello somebody
Hello [some text]

Hello mark

Dann wird dein Test wie folgt aussehen:

def test_say_hello():
    output = say_hello(lambda: "test")
    assert(output == "Hello test")

Denken Sie daran, dass Sie die IO-Funktionen einer Sprache nicht testen sollten (es sei denn, Sie sind derjenige, der die Sprache gestaltet, was eine völlig andere Situation ist).





user-input