Ne peut pas attraper moqué exception parce qu'il n'hérite pas BaseException
je travaille sur un projet qui implique de se connecter à un serveur distant, d'attendre une réponse, puis d'effectuer des actions basées sur cette réponse. Nous attrapons quelques exceptions différentes, et nous nous comportons différemment selon l'exception qui est prise. Par exemple:
def myMethod(address, timeout=20):
try:
response = requests.head(address, timeout=timeout)
except requests.exceptions.Timeout:
# do something special
except requests.exceptions.ConnectionError:
# do something special
except requests.exceptions.HTTPError:
# do something special
else:
if response.status_code != requests.codes.ok:
# do something special
return successfulConnection.SUCCESS
pour tester ceci, nous avons écrit un test comme le suivant
class TestMyMethod(unittest.TestCase):
def test_good_connection(self):
config = {
'head.return_value': type('MockResponse', (), {'status_code': requests.codes.ok}),
'codes.ok': requests.codes.ok
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.SUCCESS
)
def test_bad_connection(self):
config = {
'head.side_effect': requests.exceptions.ConnectionError,
'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError
}
with mock.patch('path.to.my.package.requests', **config):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
si j'exécute la fonction directement, tout se passe comme prévu. J'ai même testé en ajoutant raise requests.exceptions.ConnectionError
try
la clause de la fonction. Mais quand je fais les tests de mon unité, j'obtiens
ERROR: test_bad_connection (test.test_file.TestMyMethod)
----------------------------------------------------------------
Traceback (most recent call last):
File "path/to/sourcefile", line ###, in myMethod
respone = requests.head(address, timeout=timeout)
File "path/to/unittest/mock", line 846, in __call__
return _mock_self.mock_call(*args, **kwargs)
File "path/to/unittest/mock", line 901, in _mock_call
raise effect
my.package.requests.exceptions.ConnectionError
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "Path/to/my/test", line ##, in test_bad_connection
mypackage.myMethod('some_address',
File "Path/to/package", line ##, in myMethod
except requests.exceptions.ConnectionError:
TypeError: catching classes that do not inherit from BaseException is not allowed
j'ai essayé de changer l'exception que j'ai été l'application des correctifs dans BaseException
et j'ai eu une erreur plus ou moins identique.
j'ai lu https://stackoverflow.com/a/18163759/3076272 déjà, donc je pense que ça doit être un mauvais __del__
crochet de quelque part, mais je ne sais pas où chercher pour elle ou ce que je peux même le faire dans le temps de le dire. Je suis relativement nouveau à unittest.mock.patch()
il est donc très possible que je fasse quelque chose de mal.
C'est un add-in Fusion360 donc il utilise la version packagée de Python 3.3 de Fusion 360 - autant que je sache c'est une version vanille (i.e. ils ne roulent pas leur propre) mais je ne suis pas sûr de ça.
5 réponses
je pourrais reproduire l'erreur avec un exemple minimal:
foo.py:
class MyError(Exception):
pass
class A:
def inner(self):
err = MyError("FOO")
print(type(err))
raise err
def outer(self):
try:
self.inner()
except MyError as err:
print ("catched ", err)
return "OK"
Test sans moqueur :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
a = foo.A()
self.assertEquals("OK", a.outer())
Ok, tout est très bien, à la fois le test pass
Le problème vient des simulacres. Dès que la classe MyError est moqué, le expect
l'article ne peut pas attraper quoi que ce soit et je reçois la même erreur que l'exemple de la question :
class FooTest(unittest.TestCase):
def test_inner(self):
a = foo.A()
self.assertRaises(foo.MyError, a.inner)
def test_outer(self):
with unittest.mock.patch('foo.MyError'):
a = exc2.A()
self.assertEquals("OK", a.outer())
donne :
ERROR: test_outer (__main__.FooTest)
----------------------------------------------------------------------
Traceback (most recent call last):
File "...\foo.py", line 11, in outer
self.inner()
File "...\foo.py", line 8, in inner
raise err
TypeError: exceptions must derive from BaseException
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<pyshell#78>", line 8, in test_outer
File "...\foo.py", line 12, in outer
except MyError as err:
TypeError: catching classes that do not inherit from BaseException is not allowed
Ici, je reçois un premier TypeError
que vous n'avez pas eu, parce que j'élève une moquerie alors que vous avez forcé une vraie exception avec 'requests.exceptions.ConnectionError': requests.exceptions.ConnectionError
dans config. Mais le problème reste que except
l'article essaie de prendre un simulacre.
TL/DR: que vous vous moquez du requests
package except requests.exceptions.ConnectionError
clause tente d'attraper une moquerie. Comme le simulacre n'est pas vraiment un BaseException
, il provoque l'erreur.
la seule solution que je puisse imaginer est de ne pas se moquer de la totalité requests
mais seulement les parties qui ne sont pas exception. Je dois admettre que je n'ai pas pu trouver comment dire de me moquer!--34 -- > moquez tout sauf ceci mais dans votre exemple, vous n'avez besoin que de patch requests.head
. Je pense donc que cela devrait fonctionner:
def test_bad_connection(self):
with mock.patch('path.to.my.package.requests.head',
side_effect=requests.exceptions.ConnectionError):
self.assertEqual(
mypackage.myMethod('some_address',
mypackage.successfulConnection.FAILURE
)
C'est : seul patch head
méthode à l'exception comme effet secondaire.
je viens de tomber sur le même problème lors de la tentative de se moquer sqlite3
(et j'ai trouvé ce post en cherchant des solutions).
Serge dit est correct:
TL / DR: lorsque vous vous moquez du paquet full requests, the except requests.exception.La clause ConnectionError essaie d'attraper une simulation. Comme la simulation n'est pas vraiment une exception de base, elle provoque l'erreur.
la seule solution que je puisse imaginer n'est pas de se moquer de toutes les requêtes mais seulement les pièces qui ne sont pas des exceptions. Je dois admettre que je n'ai pas pu trouver comment dire de me moquer!--16 -- > moquez tout sauf ceci
ma solution a été De moquer le module entier, puis de définir l'attribut mock pour l'exception à être égal à l'exception dans la classe réelle, effectivement "se moquer" de l'exception. Par exemple, dans mon cas:
@mock.patch(MyClass.sqlite3)
def test_connect_fail(self, mock_sqlite3):
mock_sqlite3.connect.side_effect = sqlite3.OperationalError()
mock_sqlite3.OperationalError = sqlite3.OperationalError
self.assertRaises(sqlite3.OperationalError, MyClass, self.db_filename)
requests
, vous pouvez assigner des exceptions individuellement comme ceci:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
ou de le faire pour tous de l' requests
exceptions comme ceci:
mock_requests.exceptions = requests.exceptions
je ne sais pas si c'est la "bonne" façon de faire, mais jusqu'à présent, il semble fonctionner pour moi sans aucun problème.
Pour ceux d'entre nous qui ont besoin de se moquer d'une exception et ne peut pas faire simplement patcher head
, voici une solution facile qui remplace la cible d'exception avec un vide:
supposons que nous ayons une unité générique à tester, à une exception près, nous devons nous être moqués:
# app/foo_file.py
def test_me():
try:
foo()
return "No foo error happened"
except CustomError: # <-- Mock me!
return "The foo error was caught"
Nous voulons fantaisie CustomError
mais comme il s'agit d'une exception, nous rencontrons des problèmes si nous essayons de la corriger comme tout le reste. Normalement, un appel à patch
remplace la cible avec un MagicMock
mais qui ne fonctionnent pas ici. Les moqueurs sont astucieux, mais ils ne se comportent pas comme les exceptions le font. Plutôt que de réparer avec une moquerie, donnons lui une exception de talon à la place. On le fera dans notre dossier test.
# app/test_foo_file.py
from mock import patch
# A do-nothing exception we are going to replace CustomError with
class StubException(Exception):
pass
# Now apply it to our test
@patch('app.foo_file.foo')
@patch('app.foo_file.CustomError', new_callable=lambda: StubException)
def test_foo(stub_exception, mock_foo):
mock_foo.side_effect = stub_exception("Stub") # Raise our stub to be caught by CustomError
assert test_me() == "The error was caught"
# Success!
alors qu'est-ce qu'il y a avec le