¿Cómo puedo deshabilitar el registro mientras ejecuto pruebas unitarias en Python Django?

Estoy usando una prueba de unidad simple basada en un corredor de pruebas para probar mi aplicación Django.

Mi aplicación está configurada para usar un registrador básico en settings.py utilizando:

logging.basicConfig(level=logging.DEBUG) 

Y en mi código de aplicación usando:

 logger = logging.getLogger(__name__) logger.setLevel(getattr(settings, 'LOG_LEVEL', logging.DEBUG)) 

Sin embargo, cuando se ejecutan pruebas de unidad, me gustaría deshabilitar el registro para que no obstruya el resultado de mi prueba. ¿Hay una forma sencilla de desactivar el registro de forma global, de modo que los registradores específicos de la aplicación no escriban cosas en la consola cuando ejecuto las pruebas?

 logging.disable(logging.CRITICAL) 

deshabilitará todas las llamadas de registro con niveles menos graves o iguales a CRITICAL . El registro se puede volver a habilitar con

 logging.disable(logging.NOTSET) 

Ya que estás en Django, puedes agregar estas líneas a tu configuración.py:

 import sys import logging if len(sys.argv) > 1 and sys.argv[1] == 'test': logging.disable(logging.CRITICAL) 

De esa manera, no tiene que agregar esa línea en cada configuración () en sus pruebas. 🙂

También podría hacer un par de cambios prácticos para sus necesidades de prueba de esta manera.

Hay otra forma “más agradable” o “más limpia” de agregar detalles a sus pruebas y es hacer su propio corredor de prueba.

Solo crea una clase como esta:

 import logging from django.test.simple import DjangoTestSuiteRunner from django.conf import settings class MyOwnTestRunner(DjangoTestSuiteRunner): def run_tests(self, test_labels, extra_tests=None, **kwargs): # Don't show logging messages while testing logging.disable(logging.CRITICAL) return super(MyOwnTestRunner, self).run_tests(test_labels, extra_tests, **kwargs) 

Y ahora agregue a su archivo settings.py:

 TEST_RUNNER = "PATH.TO.PYFILE.MyOwnTestRunner" #(for example, 'utils.mytest_runner.MyOwnTestRunner') 

Esto te permite hacer una modificación realmente útil que el otro enfoque no hace, que es hacer que Django solo pruebe las aplicaciones que deseas. Puede hacerlo cambiando las tags de prueba agregando esta línea al corredor de prueba:

 if not test_labels: test_labels = ['my_app1', 'my_app2', ...] 

Me gusta la idea del corredor de prueba personalizado de Hassek. Cabe señalar que DjangoTestSuiteRunner ya no es el corredor de prueba predeterminado en Django 1.6+, ha sido reemplazado por DiscoverRunner . Para el comportamiento predeterminado, el corredor de prueba debería ser más como:

 import logging from django.test.runner import DiscoverRunner class NoLoggingTestRunner(DiscoverRunner): def run_tests(self, test_labels, extra_tests=None, **kwargs): # disable logging below CRITICAL while testing logging.disable(logging.CRITICAL) return super(NoLoggingTestRunner, self).run_tests(test_labels, extra_tests, **kwargs) 

¿Hay una forma sencilla de desactivar el registro de forma global, de modo que los registradores específicos de la aplicación no escriban cosas en la consola cuando ejecuto las pruebas?

Las otras respuestas evitan “escribir cosas en la consola” al configurar globalmente la infraestructura de registro para ignorar cualquier cosa. Esto funciona, pero me parece un enfoque demasiado contundente. Mi enfoque es realizar un cambio de configuración que haga solo lo necesario para evitar que los registros salgan a la consola. Así que agrego un filtro de registro personalizado a mi settings.py :

 from logging import Filter class NotInTestingFilter(Filter): def filter(self, record): # Although I normally just put this class in the settings.py # file, I have my reasons to load settings here. In many # cases, you could skip the import and just read the setting # from the local symbol space. from django.conf import settings # TESTING_MODE is some settings variable that tells my code # whether the code is running in a testing environment or # not. Any test runner I use will load the Django code in a # way that makes it True. return not settings.TESTING_MODE 

Y configuro el registro de Django para usar el filtro:

 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'filters': { 'testing': { '()': NotInTestingFilter } }, 'formatters': { 'verbose': { 'format': ('%(levelname)s %(asctime)s %(module)s ' '%(process)d %(thread)d %(message)s') }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'filters': ['testing'], 'formatter': 'verbose' }, }, 'loggers': { 'foo': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, } } 

Resultado final: cuando estoy probando, nada va a la consola, pero todo lo demás permanece igual.

¿Por qué hacer esto?

Diseño un código que contiene instrucciones de registro que se activan solo en circunstancias específicas y que deberían dar los datos exactos que necesito para el diagnóstico si las cosas van mal. Por lo tanto, pruebo que hagan lo que se supone que deben hacer y, por lo tanto, deshabilitar completamente el registro no es viable para mí. No quiero encontrar una vez que el software esté en producción que lo que pensé que se registraría no se registra.

Además, algunos corredores de prueba (Nose, por ejemplo) capturarán registros durante la prueba y emitirán la parte relevante del registro junto con una falla de prueba. Es útil para averiguar por qué falló una prueba. Si el registro está completamente apagado, no hay nada que se pueda capturar.

Existe un método bonito y limpio para suspender el registro en pruebas con el método unittest.mock.patch .

foo.py :

 import logging logger = logging.getLogger(__name__) def bar(): logger.error('There is some error output here!') return True 

tests.py :

 from unittest import mock, TestCase from foo import bar class FooBarTestCase(TestCase): @mock.patch('foo.logger', mock.Mock()) def test_bar(self): self.assertTrue(bar()) 

Y las python3 -m unittest tests no producirán resultados de registro.

Descubrí que para las pruebas dentro de un marco unittest o similar, la forma más efectiva de deshabilitar de forma segura el registro no deseado en las pruebas unitarias es habilitar / deshabilitar los métodos de setUp / tearDown de un caso de prueba particular. Esto permite que un objective específicamente donde los registros deben ser deshabilitados. También puede hacer esto explícitamente en el registrador de la clase que está probando.

 import unittest import logging class TestMyUnitTest(unittest.TestCase): def setUp(self): logging.disable(logging.CRITICAL) def tearDown(self): logging.disable(logging.NOTSET) 

A veces quieres los registros y otras veces no. Tengo este código en mi settings.py

 import sys if '--no-logs' in sys.argv: print('> Disabling logging levels of CRITICAL and below.') sys.argv.remove('--no-logs') logging.disable(logging.CRITICAL) 

Entonces, si ejecuta la prueba con las opciones --no-logs , solo obtendrá los registros critical :

 $ python ./manage.py tests --no-logs > Disabling logging levels of CRITICAL and below. 

Es muy útil si desea acelerar las pruebas en su flujo de integración continua.

Estoy usando un decorador de método simple para deshabilitar el registro solo en un método de prueba en particular.

 def disable_logging(f): def wrapper(*args): logging.disable(logging.CRITICAL) result = f(*args) logging.disable(logging.NOTSET) return result return wrapper 

Y luego lo uso como en el siguiente ejemplo:

 class ScenarioTestCase(TestCase): @disable_logging test_scenario(self): pass 

En mi caso, tengo un archivo de settings/test.py creado específicamente para fines de prueba, esto es lo que parece:

 from .base import * DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': 'test_db' } } PASSWORD_HASHERS = ( 'django.contrib.auth.hashers.MD5PasswordHasher', ) LOGGING = {} 

Puse una variable de entorno DJANGO_SETTINGS_MODULE=settings.test en /etc/environment .

Si no lo quieres, enciéndelo / apágalo repetidamente en setUp () y tearDown () para unittest (no ves la razón), solo puedes hacerlo una vez por clase:

  import unittest import logging class TestMyUnitTest(unittest.TestCase): @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) @classmethod def tearDownClass(cls): logging.disable(logging.NOTSET) 

Si tiene diferentes módulos de Initaliser para prueba, desarrollo y producción, puede deshabilitar cualquier cosa o redirigirla en el inicializador. Tengo local.py, test.py y production.py que todos heredan de common.y

common.py hace toda la configuración principal incluyendo este fragmento de código:

 LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'django.server': { '()': 'django.utils.log.ServerFormatter', 'format': '[%(server_time)s] %(message)s', }, 'verbose': { 'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s' }, 'simple': { 'format': '%(levelname)s %(message)s' }, }, 'filters': { 'require_debug_true': { '()': 'django.utils.log.RequireDebugTrue', }, }, 'handlers': { 'django.server': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'django.server', }, 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'simple' }, 'mail_admins': { 'level': 'ERROR', 'class': 'django.utils.log.AdminEmailHandler' } }, 'loggers': { 'django': { 'handlers': ['console'], 'level': 'INFO', 'propagate': True, }, 'celery.tasks': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': True, }, 'django.server': { 'handlers': ['django.server'], 'level': 'INFO', 'propagate': False, }, } 

Entonces en test.py tengo esto:

 console_logger = Common.LOGGING.get('handlers').get('console') console_logger['class'] = 'logging.FileHandler console_logger['filename'] = './unitest.log 

Esto reemplaza el controlador de la consola con un FileHandler y significa que aún se obtiene el registro pero no tengo que tocar la base de código de producción.