utilizar - try except python 3
No se puede capturar la excepción simulada porque no hereda BaseException (5)
Acabo de encontrar el mismo problema cuando me burlo de la struct
.
Me sale el error
TypeError: las clases de captura que no heredan de BaseException no están permitidas
Al intentar capturar un struct.error
elevado desde struct.unpack
.
Descubrí que la forma más sencilla de solucionar esto en mis pruebas era simplemente establecer el valor del atributo de error en mi simulacro como Exception
. Por ejemplo
El método que quiero probar tiene este patrón básico:
def some_meth(self):
try:
struct.unpack(fmt, data)
except struct.error:
return False
return True
La prueba tiene este patrón básico.
@mock.patch(''my_module.struct'')
def test_some_meth(self, struct_mock):
''''''Explain how some_func should work.''''''
struct_mock.error = Exception
self.my_object.some_meth()
struct_mock.unpack.assert_called()
struct_mock.unpack.side_effect = struct_mock.error
self.assertFalse(self.my_object.some_meth()
Esto es similar al enfoque adoptado por @BillB, pero es ciertamente más sencillo, ya que no necesito agregar importaciones a mis pruebas y seguir teniendo el mismo comportamiento. A mi parecer, esta es la conclusión lógica del hilo general del razonamiento en las respuestas aquí.
Estoy trabajando en un proyecto que implica conectarse a un servidor remoto, esperar una respuesta y luego realizar acciones basadas en esa respuesta. Capturamos un par de excepciones diferentes y nos comportamos de manera diferente según la excepción que se detecte. Por ejemplo:
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
Para probar esto, hemos escrito una prueba como la siguiente
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 ejecuto la función directamente, todo sucede como se esperaba. Incluso lo probé agregando raise requests.exceptions.ConnectionError
a la cláusula try
de la función. Pero cuando corro mis pruebas unitarias, me sale
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
Intenté cambiar la excepción que estaba BaseException
a BaseException
y obtuve un error más o menos idéntico.
Ya he leído https://stackoverflow.com/a/18163759/3076272 , así que creo que debe ser un mal __del__
gancho en alguna parte, pero no estoy seguro de dónde buscarlo o de lo que puedo hacer en el tiempo medio También soy relativamente nuevo en unittest.mock.patch()
así que es muy posible que también esté haciendo algo mal allí.
Este es un complemento de Fusion360, por lo que utiliza la versión empaquetada de Python 3.3 de Fusion 360, por lo que sé, es una versión de vainilla (es decir, no se enrolla), pero no estoy seguro de eso.
Me encontré con el mismo problema al intentar simular sqlite3
(y encontré esta publicación mientras buscaba soluciones).
Lo que Serge dijo es correcto:
TL / DR: cuando usted se burla del paquete de solicitudes completo, la cláusula de excepción requests.exceptions.ConnectionError intenta detectar un simulacro. Como el simulacro no es realmente una BaseException, causa el error.
La única solución que puedo imaginar es no simular las solicitudes completas, sino solo las partes que no son excepciones. Debo admitir que no pude encontrar la manera de decir burlarme de todo, excepto de esto.
Mi solución fue burlarse de todo el módulo, luego establecer el atributo de simulacro para que la excepción fuera igual a la excepción en la clase real, de hecho, "anulando la burla" de la excepción. Por ejemplo, en mi caso:
@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)
Para las requests
, puede asignar excepciones individualmente como esta:
mock_requests.exceptions.ConnectionError = requests.exceptions.ConnectionError
o hazlo para todas las requests
excepciones como esta:
mock_requests.exceptions = requests.exceptions
No sé si esta es la forma "correcta" de hacerlo, pero hasta ahora parece funcionar para mí sin ningún problema.
Me enfrenté a un problema similar al intentar burlarme del paquete sh . Si bien sh es muy útil, el hecho de que todos los métodos y excepciones se definan dinámicamente hace que sea más difícil burlarse de ellos. Así que siguiendo la recomendación de la documentation :
import unittest
from unittest.mock import Mock, patch
class MockSh(Mock):
# error codes are defined dynamically in sh
class ErrorReturnCode_32(BaseException):
pass
# could be any sh command
def mount(self, *args):
raise self.ErrorReturnCode_32
class MyTestCase(unittest.TestCase):
mock_sh = MockSh()
@patch(''core.mount.sh'', new=mock_sh)
def test_mount(self):
...
Para aquellos de nosotros que necesitamos simular una excepción y no podemos hacerlo simplemente parcheando la head
, aquí hay una solución fácil que reemplaza la excepción de destino con una vacía:
Digamos que tenemos una unidad genérica para probar, con una excepción que tenemos que haber burlado:
# app/foo_file.py
def test_me():
try:
foo()
return "No foo error happened"
except CustomError: # <-- Mock me!
return "The foo error was caught"
Queremos CustomError
pero como es una excepción, tenemos problemas si intentamos parchearlo como todo lo demás. Normalmente, una llamada a un patch
reemplaza al objetivo con un MagicMock
pero eso no funcionará aquí. Las simulaciones son ingeniosas, pero no se comportan como lo hacen las excepciones. En lugar de parchear con un simulacro, vamos a darle una excepción de código auxiliar en su lugar. Lo haremos en nuestro archivo de prueba.
# 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!
Entonces, ¿qué pasa con la lambda
? El new_callable
llama a lo que le demos y reemplaza el objetivo con el retorno de esa llamada. Si pasamos nuestra clase StubException
directamente, llamará al constructor de la clase y parcheará nuestro objeto de destino con una instancia de excepción en lugar de una clase que no es lo que queremos. Envolviéndolo con lambda
, devuelve nuestra clase como pretendemos.
Una vez que se realiza la stub_exception
parches, el objeto stub_exception
(que es literalmente nuestra clase StubException
) se puede levantar y capturar como si fuera el CustomError
. ¡Ordenado!
Pude reproducir el error con un ejemplo mínimo:
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"
Prueba sin burla:
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, todo está bien, ambos pasan la prueba
El problema viene con las burlas. Tan pronto como la clase MyError se burla, la cláusula de expect
no puede detectar nada y aparece el mismo error que el ejemplo de la pregunta:
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())
Inmediatamente da:
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
Aquí obtengo un primer TypeError
que no tenías, porque estoy generando un simulacro mientras forzaste una verdadera excepción con ''requests.exceptions.ConnectionError'': requests.exceptions.ConnectionError
en la configuración. Pero el problema sigue siendo que la cláusula de except
intenta atrapar una burla .
TL / DR: cuando usted se burla del paquete de requests
completo, la cláusula de except requests.exceptions.ConnectionError
intenta detectar un simulacro. Como el simulacro no es realmente una BaseException
, causa el error.
La única solución que puedo imaginar es no simular las requests
completas, sino solo las partes que no son excepciones. Debo admitir que no pude encontrar la manera de decir que se burlaba de todo, excepto de esto, pero en su ejemplo, solo necesita parchear las requests.head
. Así que creo que esto debería funcionar:
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
)
Es decir: solo parchea el método de la head
con la excepción como efecto secundario.