2014-01-10 25 views
17

Angenommen, ich habe diesen Python-Code spöttisch:Python roh Eingang in Unittests

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 einen Unittest für das? Ich weiß, dass ich 'Mock' benutzen muss, aber ich verstehe nicht wie. Kann jemand ein einfaches Beispiel machen?

+0

möglich Duplikat [Versorgungseingänge zu Python Unittests] (http://stackoverflow.com/questions/2617057/supply -inputs-to-python-unittests – jonrsharpe

+0

Ich kann die Antwort nicht finden – user3156971

+1

Eine der drei Antworten ist * wörtlich * über die Verwendung von 'Mock' zu testen' raw_input' – jonrsharpe

Antwort

24

Sie können die Eingabe nicht packen, aber Sie können sie umbrechen, 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 diese Schnipsel nur in Python-Versionen 3.3+

+8

@ArtOfWarfare Mock ist neu in python3.3 https: //docs.python.org/3/library/unittest.mock.html Es gibt einen Backport https://pypi.python.org/pypi/mock – gawel

+1

Sie sollten die Python-Version in Ihrer Antwort angeben. Danke @gawel –

+1

Sie müssen keine Eingabe umbrechen. '@patch ('builtins.input', return_value = 'yes')' sollte den Trick machen. – Finn

0
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 
18

Güter arbeiten, zunächst einmal, ich fühle es notwendig ist, darauf hinzuweisen, dass im ursprünglichen Code in Frage, es gibt tatsächlich zwei Dinge, die in Angriff genommen werden müssen:

  1. raw_input (ein Eingang Nebeneffekt) muss verspottet werden.
  2. print (ein Ausgang Nebeneffekt) muss überprüft werden.

In einer idealen Funktion zur Prüfeinheit, gäbe es keine Nebenwirkungen. Eine Funktion würde einfach getestet, indem Argumente übergeben und ihre Ausgabe überprüft wird. Aber oft wollen wir Funktionen testen, die nicht ideal sind, IE, in Funktionen wie Ihrer.

Was sollen wir tun? Nun, in Python 3.3 wurden beide Probleme, die ich oben aufgelistet habe, trivial, weil das Modul die Fähigkeit erhielt, nachzuspielen und nach Nebenwirkungen zu suchen. Aber zu Beginn des Jahres 2014 waren nur 30% der Python-Programmierer auf 3.x umgestiegen, und um der anderen 70% der Python-Programmierer, die noch 2.x verwenden, zu folgen, werde ich eine Antwort skizzieren. Bei der aktuellen Rate wird 3.x 2.x nicht vor ~ 2019 überholen und 2.x wird nicht vor ~ 2027 verschwinden. Ich denke, diese Antwort wird noch einige Jahre nützlich sein.

Ich möchte die Probleme über einer nach dem anderen aufgeführten Adresse, also werde ich zunächst Ihre Funktion ändern verwenden print als Ausgabe return zu 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' 

Also alles, was wir mock raw_input tun müssen. Einfach genug - Omid Raha's answer to this very question zeigt uns, wie man das macht, indem man die __builtins__.raw_input Implementierung mit unserer Scheinimplementierung austauscht. Außer seiner Antwort war nicht richtig in eine TestCase organisiert und funktioniert, also 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 auf Python Namenskonventionen - Variablen, die vom Parser erforderlich sind, aber nicht verwendet werden _ typischerweise genannt, wie im Fall der nicht verwendeten Variable Lambda (die in der Regel ist die Aufforderung an den Benutzer gezeigt in der Fall der raw_input, falls Sie sich wundern, warum es in diesem Fall überhaupt erforderlich ist).

Wie auch immer, das ist unordentlich und redundant. Also werde ich mit der Wiederholung durch Hinzufügen einer contextmanager, die für einfache with Aussagen ermöglichen, wegräumen.

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

denke ich, dass schön, den ersten Teil dieser Antworten. Weiter zum zweiten Teil - Überprüfung print.Ich fand das viel schwieriger - ich würde gerne hören, wenn jemand eine bessere Antwort hat.

Anyways, die print Anweisung kann nicht außer Kraft gesetzt werden, aber wenn Sie print() Funktionen verwenden, anstatt (wie Sie sollte) und from __future__ import print_function können Sie die folgende 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') 

Der schwierige Bit ist, dass Sie brauchen zu yield eine Antwort vor dem with Block eingegeben wird. Aber Sie können nicht wissen, was diese Antwort sein wird, bis der print() innerhalb des with Blockes aufgerufen wird. Dies wäre in Ordnung, wenn Strings änderbar wären, aber das sind sie nicht. Also wurde stattdessen eine kleine Versprechen oder Proxy-Klasse gemacht - PromiseString. Es macht nur zwei Dinge - erlauben Sie eine Zeichenfolge (oder irgendetwas, wirklich) zu setzen und lassen Sie uns wissen, ob es gleich einer anderen Zeichenfolge ist. A PromiseString wird yield ed und dann auf den Wert gesetzt, der normalerweise print innerhalb des with Blocks wäre.

Hoffentlich schätze ich all diese Tricks, die ich geschrieben habe, da ich heute Abend etwa 90 Minuten brauchte. Ich habe den ganzen Code getestet und verifiziert, dass alles mit Python 2.7 funktioniert hat.

5

Gerade lief über das gleiche Problem, aber ich spottete nur __builtin__.raw_input.

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

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 mit der Bibliothek genty, können Sie die beiden Tests 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') 
2

ich verwende Python 3.4 und hatte Antworten oben anzupassen. Meine Lösung faktorisiert den gemeinsamen Code in die benutzerdefinierte runTest-Methode und zeigt Ihnen, wie Sie beide input() und print() patchen. Hier ist Code, der ausgeführt wird: Import Unittest von io importieren StringIO von unittest.mock Import 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 __name__ == '__main__': 
    unittest.main() 
+1

Ich habe meine Antwort für mein Setup mit 'nose2' angepasst (ohne' unittest' direkt), und das hat mir gut getan. Eine Sache zu beachten ist, dass, wenn Sie 'fakeout.getvalue()' zu 'fakeout.getvalue(). Strip()' ändern, Sie vermeiden können, die zusätzliche neue Zeile zu übergeben. –