wake-up-neo.com

Mock Python's integrierte Druckfunktion

Ich habe es versucht 

from mock import Mock
import __builtin__

__builtin__.print = Mock()

Dies führt jedoch zu einem Syntaxfehler. Ich habe es auch so versucht

@patch('__builtin__.print')
def test_something_that_performs_lots_of_prints(self, mock_print):

    # assert stuff

Gibt es eine Möglichkeit, dies zu tun? 

35
aychedee

print ist ein Schlüsselwort in Python 2.x, dessen Verwendung als Attribut einen SyntaxError auslöst. Sie können dies vermeiden, indem Sie from __future__ import print_function am Anfang der Datei verwenden.

Hinweis: Sie können setattr nicht einfach verwenden, da die von Ihnen geänderte Druckfunktion nur aufgerufen wird, wenn die print-Anweisung deaktiviert ist.

Bearbeiten: Sie müssen auch from __future__ import print_function in jeder Datei, in der Ihre geänderte print-Funktion verwendet werden soll, verwenden, oder sie wird durch die print-Anweisung maskiert.

15
quantum

Ich weiß, dass es bereits eine akzeptierte Antwort gibt, aber es gibt eine einfachere Lösung für dieses Problem - den Druck in Python 2.x zu verspotten. Antwort ist in der Mock Library Tutorial: http://www.voidspace.org.uk/python/mock/patch.html und es ist:

>>> from StringIO import StringIO
>>> def foo():
...     print 'Something'
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

Natürlich können Sie auch folgende Behauptung verwenden:

self.assertEqual("Something\n", mock_stdout.getvalue())

Ich habe diese Lösung in meinen Tests überprüft und funktioniert wie erwartet. Hoffe das hilft jemandem. Prost!

44

Dies ist eine viel einfachere Python 3-Lösung - es ist einfacher, unittest.mock direkt in der eingebauten print-Funktion zu verwenden, anstatt mit sys.stdout herumzuspielen:

from unittest.mock import patch, call

@patch('builtins.print')
def test_print(mocked_print):
    print('foo')
    print()

    assert mocked_print.mock_calls == [call('foo'), call()]
5
Tom

Wenn Sie die print-Anweisung von 2.x gegenüber der print()-Funktion von 2.x beibehalten möchten, können Sie stattdessen Ihren sys.stdout überlisten.

Schreiben Sie eine Dummy-Datei, etwa auf folgende Weise:

class Writable(object):
    """Class which has the capability to replace stdout."""
    newwrite = None
    def __init__(self, oldstdout, newwrite=None):
        self.oldstdout = oldstdout
        if newwrite is not None:
            self.newwrite = newwrite
    def write(self, data):
        self.newwrite(self.oldstdout, data)
    @classmethod
    def subclass(cls, writefunc):
        newcls = type('', (cls,),
            dict(write=lambda self, data: writefunc(self.oldstdout, data)
        return newcls

Diese Klasse erwartet eine Kombination mit einer Schreibfunktion, die die gedruckten Daten abruft. Diese Schreibfunktion soll zwei Argumente annehmen: das erste mit dem "old stdout", das am Ende für den Druck verwendet werden soll, und ein weiteres für die Daten.

Lass uns nehmen

def mywrite(sink, data):
    sink.write(data.encode("hex"))

dafür.

Jetzt können Sie tun

import sys
sys.stdout = Writable(sys.stdout, mywrite)

oder du kannst es tun

@Writable.subclass
def mywritable(sink, data)
    sink.write(data.encode("hex"))

sys.stdout = mywritable(sys.stdout)

Die 2. Version ist etwas kniffeliger: Sie erstellt eine Unterklasse der Writable mit Hilfe einer Decorator-Funktion, die die angegebene Funktion in eine Methode der neu erstellten Klasse umwandelt und in den Namen der angegebenen Funktion setzt.

Danach haben Sie eine neue Klasse, die mit dem "alten stdout" als Argument instanziiert werden kann und anschließend sys.stdout ersetzen kann.

3
glglgl

Meine Version.

Im getesteten Programm (zB: pp.py):

from __future__ import print_function

def my_func():
    print('hello')

Im Testprogramm:

def test_print(self):
    from pp import my_func
    from mock import call
    with mock.patch('__builtin__.print') as mock_print:
       my_func()
       mock_print.assert_has_calls(
            [
                call('hello')
            ]
        )
3
Chien-Wei Huang
import mock
import sys

mock_stdout = mock.Mock()
sys.stdout = mock_stdout
print 'Hello!'
sys.stdout = sys.__stdout__

print mock_stdout.mock_calls
[call.write('Hello!'), call.write('\n')]
1
sgjurano

Dieses Python 3-Beispiel baut auf der Python 2-Antwort von Krzysztof auf. Es verwendet unittest.mock. Es verwendet eine wiederverwendbare Hilfsmethode, um die Assertion durchzuführen.

import io
import unittest
import unittest.mock

from .solution import fizzbuzz


class TestFizzBuzz(unittest.TestCase):

    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def assert_stdout(self, n, expected_output, mock_stdout):
        fizzbuzz(n)
        self.assertEqual(mock_stdout.getvalue(), expected_output)

    def test_only_numbers(self):
        self.assert_stdout(2, '1\n2\n')
0
A-B-B

Zuerst heißt das Modul __builtins__ und Sie müssen es nicht importieren.

In Python 2 ist print ein Schlüsselwort, sodass Sie es nicht direkt als Attributnamen verwenden können. Sie können setattr/getattr verwenden, um dies zu umgehen:

getattr(__builtins__, "print")

Eine weitere Option ist die Verwendung von from __future__ import print_function, mit dem die Parsing-Syntax des Moduls in Python 3-Syntax geändert wird.

0
lqc

Wie lcq sagt, ist print ein Schlüsselwort. Überlegen Sie sich also, was es bedeuten würde, wenn Sie tatsächlich Druckvorgänge unter Python 2.7.3 durchführen. Sie hätten folgenden Code:

print "Hi."

sich in etwas verwandeln:

<MagicMock id='49489360'> "Hi."

Auf MagicMock-Objekte kann nicht auf diese Weise zugegriffen werden. Sie erhalten daher einen Syntaxfehler.

Also ... ja. Sie können nur die Python3-Funktion function oder sys.stdout simulieren.

0
dbn