2016-08-09 11 views
1

Ich habe Probleme, Ausnahmen zu testen, die in einem mit in Python 3.4 ausgelöst werden würde. Ich kann nur erhalten nicht die Tests für diesen Frieden von Code ausführen:Python 3: Wie testet man Ausnahmen innerhalb mit?

import logging 
... 
class Foo(object): 
    ... 
    def foo(self, src, dst): 
     try: 
      with pysftp.Connection(self._host, username=self._username, password=self._password) as connection: 
       connection.put(src, dst) 
       connection.close() 
     except (
       ConnectionException, 
       CredentialException, 
       SSHException, 
       AuthenticationException, 
       HostKeysException, 
       PasswordRequiredException 
     ) as e: 
      self._log.error(e) 

Und das ist, wie ich will, es testen:

import logging 
... 
class TestFoo(TestCase): 
    @parameterized.expand([ 
     ('ConnectionException', ConnectionException), 
     ('CredentialException', CredentialException), 
     ('SSHException', SSHException), 
     ('AuthenticationException', AuthenticationException), 
     ('HostKeysException', HostKeysException), 
     ('PasswordRequiredException', PasswordRequiredException), 
    ]) 
    @patch('pysftp.Connection', spec_set=pysftp.Connection) 
    def test_foo_exceptions(self, _, ex, sftp_mock): 
     """ 
     NOTE: take a look at: 
       http://stackoverflow.com/questions/37014904/mocking-python-class-in-unit-test-and-verifying-an-instance 
       to get an understanding of __enter__ and __exit__ 
     """ 
     sftp_mock.return_value = Mock(
      spec=pysftp.Connection, 
      side_effect=ex, 
      __enter__ = lambda self: self, 
      __exit__ = lambda *args: None 
     ) 
     foo = Foo('host', 'user', 'pass', Mock(spec_set=logging.Logger)) 
     foo.foo('src', 'dst') 
     self.assertEqual(foo._log.error.call_count, 1) 

aber es funktioniert nicht - Ausgang:

Failure 
... 
AssertionError: 0 != 1 
+0

Meinen Sie 'Ausnahmen in 'pysftp.Connection' Instanziierung' oder beim Aufruf von' connection.something() '? – ForceBru

+0

Was meinst du mit * mit einem 'mit' *? Was löst die Ausnahme aus? Der Kontextmanager, den Sie für die Verwendung in 'with' erstellen? Oder soll der Kontextmanager eine Ausnahme behandeln, die in dem mit 'mit' verwalteten Block ausgelöst wird? –

+0

Sie lösen die Ausnahme als Nebeneffekt von * creating * Ihrem Kontextmanager aus (der 'pysftp.Connection (..)' Aufruf löst dies aus). Die 'with'-Anweisung kann sie niemals als Kontextmanager verwenden, noch wird die von 'with' gemanagte Blockade jemals ausgeführt. Ihr Code übt also die Anweisung 'try: ... except ..' korrekt aus, aber Sie benötigen keinerlei Kenntnisse des Kontextmanagers. Was dann falsch läuft, ist, wie der Logger aufgerufen wird. –

Antwort

1

Ihr sftp_mock.return_value Objekt wird nie aufgerufen, so dass die side_effect nie ausgelöst wird und keine Ausnahme ausgelöst wird. Es würde nur aufgerufen, wenn der Rückgabewert von pysftp.Connection(...) selbst erneut aufgerufen wird.

den Nebeneffekt Set direkt auf dem Mock:

sftp_mock.side_effect = ex 

Beachten Sie, dass jetzt der pysftp.Connection(...) Ausdruck, der die Ausnahme auslöst und es nicht mehr wichtig, dass der Rückgabewert dieses Ausdrucks wurde als Kontext verwendet würde Manager in einer with Aussage.

Beachten Sie, dass sich Ihre Ausnahmen darüber beschweren, keine Argumente zu erhalten; Pass in Instanzen Ihre Ausnahmen, nicht den Typ:

@parameterized.expand([ 
    ('ConnectionException', ConnectionException('host', 1234)), 
    # ... etc. 
]) 
+0

ergibt: 'TypeError: __init __() fehlt 2 erforderliche positionale Argumente: 'host' und 'port'' –

+0

@LarsGrundei: ah, deine * exception * löst das aus; Sie müssen Instanzen bereitstellen, nicht die Klasse. Sie können diesen Fehler beispielsweise mit 'raise pysftp.ConnectionException' reproduzieren. –

+0

Vielen Dank, funktioniert :) –