web-dev-qa-db-ger.com

python spottet raw_input in Unit-Tests

Angenommen, ich habe diesen Python-Code:

def answer():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        print 'you entered yes'
    if ans == 'no':
        print 'you entered no'

Wie schreibe ich ein Testgerät? Ich weiß, ich muss 'Mock' verwenden, aber ich verstehe nicht wie. Kann jemand ein einfaches Beispiel machen?

25
user3156971

Sie können die Eingabe nicht patchen, aber Sie können sie umwickeln, um mock.patch () zu verwenden. Hier ist eine Lösung:

from unittest.mock import patch
from unittest import TestCase


def get_input(text):
    return input(text)


def answer():
    ans = get_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'


class Test(TestCase):

    # get_input will return 'yes' during this test
    @patch('yourmodule.get_input', return_value='yes')
    def test_answer_yes(self, input):
        self.assertEqual(answer(), 'you entered yes')

    @patch('yourmodule.get_input', return_value='no')
    def test_answer_no(self, input):
        self.assertEqual(answer(), 'you entered no')

Beachten Sie, dass dieses Snippet nur in Python-Versionen 3.3 und höher funktioniert

34
gawel

Okay, zunächst einmal möchte ich darauf hinweisen, dass es im fraglichen Originalcode tatsächlich zwei Dinge gibt, die angegangen werden müssen:

  1. raw_input (ein Nebeneffekt der Eingabe) muss verspottet werden.
  2. print (ein Nebeneffekt der Ausgabe) muss überprüft werden.

Bei einer idealen Funktion für das Testen von Einheiten treten keine Nebenwirkungen auf. Eine Funktion würde einfach durch Eingabe von Argumenten getestet und deren Ausgabe geprüft. Aber häufig möchten wir Funktionen testen, die nicht ideal sind, dh in Funktionen wie Ihrer.

Also, was sollen wir tun? Nun, in Python 3.3 wurden beide der oben genannten Probleme trivial, da das Modul unittest die Fähigkeit erhielt, sich zu verspotten und auf Nebenwirkungen zu prüfen. Seit Anfang 2014 waren jedoch nur 30% der Python-Programmierer auf 3.x umgestiegen. Für die anderen 70% der Python-Programmierer, die noch 2.x verwenden, werde ich eine Antwort geben. Bei der aktuellen Rate überholt 2.x 2.x bis ~ 2019 nicht und 2.x verschwindet erst ~ 2027. Ich denke, diese Antwort wird für mehrere Jahre nützlich sein. 

Ich möchte die oben aufgeführten Probleme nacheinander ansprechen, daher werde ich zunächst Ihre Funktion ändern, indem Sie print als Ausgabe in return verwenden. Keine Überraschungen, hier ist der Code:

def answerReturn():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'

Wir müssen also nur raw_input nachahmen. Einfach genug - Die Antwort von Omid Raha auf diese Frage zeigt uns, wie man das macht, indem man die __builtins__.raw_input-Implementierung mit unserer Mock-Implementierung durchdringt. Abgesehen davon, dass seine Antwort nicht ordnungsgemäß in einer TestCase-Funktion organisiert war und Funktionen ausgeführt wurden, werde ich das demonstrieren.

import unittest    

class TestAnswerReturn(unittest.TestCase):
    def testYes(self):
        original_raw_input = __builtins__.raw_input
        __builtins__.raw_input = lambda _: 'yes'
        self.assertEqual(answerReturn(), 'you entered yes')
        __builtins__.raw_input = original_raw_input

    def testNo(self):
        original_raw_input = __builtins__.raw_input
        __builtins__.raw_input = lambda _: 'no'
        self.assertEqual(answerReturn(), 'you entered no')
        __builtins__.raw_input = original_raw_input

Kleine Anmerkung nur zu Python-Namenskonventionen - Vom Parser benötigte, aber nicht verwendete Variablen werden normalerweise _ genannt, wie im Fall der nicht verwendeten Variablen des Lambdas (normalerweise die Eingabeaufforderung, die dem Benutzer im Fall raw_input angezeigt wird) Sie fragen sich, warum es in diesem Fall überhaupt erforderlich ist.

Das ist sowieso unordentlich und überflüssig. Deshalb werde ich die Wiederholung durch Hinzufügen einer contextmanager aufheben, die einfache with-Anweisungen zulässt.

from contextlib import contextmanager

@contextmanager
def mockRawInput(mock):
    original_raw_input = __builtins__.raw_input
    __builtins__.raw_input = lambda _: mock
    yield
    __builtins__.raw_input = original_raw_input

class TestAnswerReturn(unittest.TestCase):
    def testYes(self):
        with mockRawInput('yes'):
            self.assertEqual(answerReturn(), 'you entered yes')

    def testNo(self):
        with mockRawInput('no'):
            self.assertEqual(answerReturn(), 'you entered no')

Ich denke, das beantwortet den ersten Teil gut. Weiter zum zweiten Teil - print prüfen. Ich fand das viel schwieriger - ich würde gerne hören, wenn jemand eine bessere Antwort hat.

Die print-Anweisung kann nicht überschrieben werden. Wenn Sie jedoch print()-Funktionen (wie Sie sollten) und from __future__ import print_function verwenden, können Sie Folgendes verwenden:

class PromiseString(str):
    def set(self, newString):
        self.innerString = newString

    def __eq__(self, other):
        return self.innerString == other

@contextmanager
def getPrint():
    promise = PromiseString()
    original_print = __builtin__.print
    __builtin__.print = lambda message: promise.set(message)
    yield promise
    __builtin__.print = original_print

class TestAnswer(unittest.TestCase):
    def testYes(self):
        with mockRawInput('yes'), getPrint() as response:
            answer()
            self.assertEqual(response, 'you entered yes')

    def testNo(self):
        with mockRawInput('no'), getPrint() as response:
            answer()
            self.assertEqual(response, 'you entered no')

Das schwierige an dieser Stelle ist, dass Sie eine yield-Antwort benötigen, bevor der with-Block eingegeben wird. Sie können jedoch nicht wissen, wie diese Antwort aussehen wird, bis die print() im with-Block aufgerufen wird. Dies wäre in Ordnung, wenn die Zeichenketten veränderbar wären, aber nicht. Stattdessen wurde ein kleines Versprechen oder eine Proxy-Klasse gemacht - PromiseString. Es gibt nur zwei Dinge: Erlauben Sie, dass eine Zeichenfolge (oder irgendetwas, wirklich) gesetzt wird, und lassen Sie uns wissen, ob sie einer anderen Zeichenfolge entspricht. Eine PromiseString ist yielded und wird dann auf den Wert gesetzt, der normalerweise print im with-Block wäre.

Ich hoffe, Sie schätzen all die Tricks, die ich geschrieben habe, zu schätzen, da ich heute Abend etwa 90 Minuten gebraucht habe. Ich habe den gesamten Code getestet und festgestellt, dass alles mit Python 2.7 funktioniert.

27
ArtOfWarfare

Ich bin nur auf dasselbe Problem gestoßen, aber ich habe __builtin__.raw_input verspottet.

Nur auf Python 2 getestet. pip install mock, wenn das Paket noch nicht installiert ist.

from mock import patch
from unittest import TestCase

class TestAnswer(TestCase):
    def test_yes(self):
        with patch('__builtin__.raw_input', return_value='yes') as _raw_input:
            self.assertEqual(answer(), 'you entered yes')
            _raw_input.assert_called_once_with('enter yes or no')

    def test_no(self):
        with patch('__builtin__.raw_input', return_value='no') as _raw_input:
            self.assertEqual(answer(), 'you entered no')
            _raw_input.assert_called_once_with('enter yes or no')

Alternativ können Sie die Bibliothek genty verwenden, um die beiden Tests zu vereinfachen:

from genty import genty, genty_dataset
from mock import patch
from unittest import TestCase

@genty
class TestAnswer(TestCase):
    @genty_dataset(
        ('yes', 'you entered yes'),
        ('no', 'you entered no'),
    )
    def test_answer(self, expected_input, expected_answer):
        with patch('__builtin__.raw_input', return_value=expected_input) as _raw_input:
            self.assertEqual(answer(), expected_answer)
            _raw_input.assert_called_once_with('enter yes or no')
6
Jeff-Meadows

Ich benutze Python 3.4 und musste die Antworten oben anpassen. Meine Lösung berücksichtigt den allgemeinen Code in der benutzerdefinierten runTest-Methode und zeigt Ihnen, wie Sie sowohl input() als auch print() patchen. Hier ist der Code, der ausgeführt wird: Importieren Sie das Einzige von io importieren Sie StringIO von unittest.mock importieren Sie Patch

def answer():
    ans = input('enter yes or no')
    if ans == 'yes':
        print('you entered yes')
    if ans == 'no':
        print('you entered no')


class MyTestCase(unittest.TestCase):
    def runTest(self, given_answer, expected_out):
        with patch('builtins.input', return_value=given_answer), patch('sys.stdout', new=StringIO()) as fake_out:
            answer()
            self.assertEqual(fake_out.getvalue().strip(), expected_out)

    def testNo(self):
        self.runTest('no', 'you entered no')

    def testYes(self):
        self.runTest('yes', 'you entered yes')

if __== '__main__':
    unittest.main()
5
tbc0
def answer():
    ans = raw_input('enter yes or no')
    if ans == 'yes':
        return 'you entered yes'
    if ans == 'no':
        return 'you entered no'


def test_answer_yes():
    assert(answer() == 'you entered yes')

def test_answer_no():
    assert(answer() == 'you entered no')

Origin_raw_input = __builtins__.raw_input

__builtins__.raw_input = lambda x: "yes"
test_answer_yes()

__builtins__.raw_input = lambda x: "no"
test_answer_no()

__builtins__.raw_input = Origin_raw_input
0
Omid Raha

Das mache ich in Python 3 :

class MockInputFunction:
    def __init__(self, return_value=None):
        self.return_value = return_value
        self._orig_input_fn = __builtins__['input']

    def _mock_input_fn(self, Prompt):
        print(Prompt + str(self.return_value))
        return self.return_value

    def __enter__(self):
        __builtins__['input'] = self._mock_input_fn

    def __exit__(self, type, value, traceback):
        __builtins__['input'] = self._orig_input_fn

die dann in jedem kontext verwendet werden kann. Beispielsweise verwendet pytest gewöhnliche assert -Anweisungen.

def func():
    """ function to test """
    x = input("What is x? ")
    return int(x)

# to test, you could simply do:
with MockInputFunction(return_value=13):
    assert func() == 13


0
Kris