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?
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
Okay, zunächst einmal möchte ich darauf hinweisen, dass es im fraglichen Originalcode tatsächlich zwei Dinge gibt, die angegangen werden müssen:
raw_input
(ein Nebeneffekt der Eingabe) muss verspottet werden.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 yield
ed 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.
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')
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()
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
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