Come posso disabilitare la registrazione mentre eseguo i test unitari in Python Django?


168

Sto usando un semplice test runner basato su unit test per testare la mia applicazione Django.

La mia stessa applicazione è configurata per usare un logger di base in settings.py usando:

logging.basicConfig(level=logging.DEBUG)

E nel mio codice dell'applicazione usando:

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

Tuttavia, quando eseguo unittest, desidero disabilitare la registrazione in modo da non ingombrare l'output del risultato del test. Esiste un modo semplice per disattivare la registrazione in modo globale, in modo che i logger specifici dell'applicazione non stiano scrivendo elementi sulla console quando eseguo i test?


Come hai abilitato la registrazione durante l'esecuzione dei test? e perché non stai usando django LOGGING?
dalore,

Risposte:


249
logging.disable(logging.CRITICAL)

disabiliterà tutte le chiamate di accesso con livelli meno gravi o uguali a CRITICAL. La registrazione può essere riattivata con

logging.disable(logging.NOTSET)

42
Questo può essere ovvio, ma trovo utile a volte dichiarare l'ovvio a beneficio di altri lettori: dovresti mettere la chiamata logging.disable(dalla risposta accettata) nella parte superiore della tests.pytua applicazione che sta eseguendo la registrazione.
CJ Gaconnet

7
Ho finito per mettere la chiamata in setUp () ma il tuo punto è ben accolto.
shreddd,

nel metodo setUp () del test o nel test effettivo che genera i messaggi di registro che si desidera nascondere.
Qris

10
E nel tuo tearDown()metodo: logging.disable(logging.NOTSET)rimette la registrazione in posizione ordinatamente.
mlissner

34
Inserirlo nel file init .py del testsmodulo è molto utile.
toabi,

46

Dato che sei in Django, puoi aggiungere queste righe alle tue settings.py:

import sys
import logging

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

In questo modo non è necessario aggiungere quella riga in ogni setUp()test.

In questo modo puoi anche fare un paio di utili modifiche per le tue esigenze di test.

C'è un altro modo "più bello" o "più pulito" per aggiungere dettagli ai tuoi test e che sta facendo il tuo test runner.

Basta creare una classe come questa:

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)

E ora aggiungi al tuo file settings.py:

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

Ciò ti consente di apportare una modifica davvero utile che l'altro approccio non prevede, ovvero fare in modo che Django collauda semplicemente le applicazioni desiderate. Puoi farlo modificando l' test_labelsaggiunta di questa riga al test runner:

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

Certo, metterlo in settings.py lo renderebbe globale.
shreddd,

7
per Django 1.6+, controlla la risposta di @alukach.
Hassek,

2
A volte nei test unitari, voglio affermare che è stato registrato un errore, quindi questo metodo non è l'ideale. Comunque, è una buona risposta.
Sardathrion - contro l'abuso da SE del

23

Esiste un modo semplice per disattivare la registrazione in modo globale, in modo che i logger specifici dell'applicazione non stiano scrivendo elementi sulla console quando eseguo i test?

Le altre risposte impediscono di "scrivere materiale sulla console" impostando globalmente l'infrastruttura di registrazione in modo da ignorare qualsiasi cosa. Funziona ma trovo un approccio troppo schietto. Il mio approccio è quello di eseguire una modifica della configurazione che fa solo ciò che è necessario per impedire ai log di uscire sulla console. Quindi aggiungo un filtro di registrazione personalizzato al mio 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

E configuro la registrazione Django per utilizzare il 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,
        },
    }
}

Risultato finale: quando sto testando, nulla va alla console, ma tutto il resto rimane lo stesso.

Perché farlo?

Progetto codice che contiene istruzioni di registrazione che vengono attivate solo in circostanze specifiche e che dovrebbero generare i dati esatti di cui ho bisogno per la diagnosi in caso di problemi. Pertanto ho testato che fanno quello che dovrebbero fare e la registrazione così completamente la disabilitazione non è praticabile per me. Una volta che il software è in produzione, non voglio trovare quello che pensavo sarebbe stato registrato non è registrato.

Inoltre, alcuni runner di test (Nose, ad esempio) acquisiranno i log durante i test e produrranno la parte pertinente del log insieme a un errore del test. È utile per capire perché un test non è riuscito. Se la registrazione è completamente disattivata, non è possibile acquisire nulla.


"Qualsiasi runner di test che utilizzo caricherà il codice Django in modo da renderlo vero." Interessante ... Come?
webtweakers

Ho un test_settings.pyfile che si trova accanto a quello del mio progetto settings.py. È impostato per caricare settings.pye apportare alcune modifiche come impostato TESTING_MODEsu True. I miei test runner sono organizzati in modo tale che test_settingssia il modulo caricato per le impostazioni del progetto Django. Ci sono molti modi per farlo. Di solito vado con l'impostazione della variabile di ambiente DJANGO_SETTINGS_MODULEsu proj.test_settings.
Louis

Questo è fantastico e fa esattamente quello che voglio. Nasconde la registrazione durante gli unittest fino a quando qualcosa non riesce - quindi Django Nose raccoglie l'output e lo stampa con l'errore. Perfetto. Combinalo con questo per determinare se il test unitario è attivo.
rrauenza,

21

Mi piace l'idea del test runner personalizzato di Hassek. Va notato che DjangoTestSuiteRunnernon è più il test runner predefinito in Django 1.6+, è stato sostituito dal DiscoverRunner. Per comportamento predefinito, il test runner dovrebbe essere più simile a:

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)

Ho trovato la tua soluzione dopo aver provato molte cose. Tuttavia, non sono in grado di impostare la variabile TEST_RUNNER nelle impostazioni in quanto non è in grado di importare il modulo in cui si trova il file test_runner.
Bunny Rabbit,

Sembra un problema di importazione. Stai impostando TEST_RUNNER su un percorso di stringa per il runner (non il modulo Python effettivo)? Inoltre, dove si trova il tuo corridore? Ho il mio in un'app separata denominata helpers, che ha solo programmi di utilità che non importano da nessun'altra parte del progetto.
alukach,

5

Ho scoperto che per i test all'interno unittesto simili di un framework, il modo più efficace per disabilitare in modo sicuro la registrazione indesiderata nei test unitari è abilitare / disabilitare i metodi setUp/ tearDowndi un particolare caso di test. Ciò consente una destinazione specifica in cui i registri devono essere disabilitati. Puoi anche farlo esplicitamente sul logger della classe che stai testando.

import unittest
import logging

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

    def tearDown(self):
        logging.disable(logging.NOTSET)

4

Sto usando un semplice decoratore di metodi per disabilitare la registrazione solo in un particolare metodo di prova.

def disable_logging(f):

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

        return result

    return wrapper

E poi lo uso come nel seguente esempio:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass

3

Esiste un metodo carino e pulito per sospendere la registrazione nei test con il unittest.mock.patchmetodo.

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())

E python3 -m unittest testsnon produrrà alcun output di registrazione.


1

A volte vuoi i log e altre volte no. Ho questo codice nel miosettings.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)

Quindi se esegui il test con le --no-logsopzioni otterrai solo i criticalregistri:

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

È molto utile se si desidera accelerare i test sul flusso di integrazione continua.


1

Se non lo vuoi attivare / disattivare ripetutamente in setUp () e tearDown () per unittest (non vedi il motivo), puoi farlo una volta per classe:

    import unittest
    import logging

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

1

Nei casi in cui desidero temporaneamente eliminare un logger specifico, ho scritto un piccolo gestore di contesto che ho trovato utile:

from contextlib import contextmanager
import logging

@contextmanager
def disable_logger(name):
    """Temporarily disable a specific logger."""
    logger = logging.getLogger(name)
    old_value = logger.disabled
    logger.disabled = True
    try:
        yield
    finally:
        logger.disabled = old_value

Quindi lo usi come:

class MyTestCase(TestCase):
    def test_something(self):
        with disable_logger('<logger name>'):
            # code that causes the logger to fire

Ciò ha il vantaggio che il logger viene riattivato (o riportato allo stato precedente) una volta withcompletato.


1

Puoi inserirlo nella directory di livello superiore per il __init__.pyfile dei test unitari . Ciò disabiliterà la registrazione a livello globale nella suite di unit test.

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

0

Nel mio caso ho un file di impostazioni settings/test.pycreato appositamente per scopi di test, ecco come appare:

from .base import *

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': 'test_db'
    }
}

PASSWORD_HASHERS = (
    'django.contrib.auth.hashers.MD5PasswordHasher',
)

LOGGING = {}

Ho inserito una variabile d'ambiente DJANGO_SETTINGS_MODULE=settings.testin /etc/environment.


0

Se hai diversi moduli initaliser per test, sviluppo e produzione, puoi disabilitare qualsiasi cosa o reindirizzarlo nel sigla. Ho local.py, test.py e production.py che tutti ereditano da common.y

common.py esegue tutte le principali configurazioni, incluso questo frammento:

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,
    },
}

Quindi in test.py ho questo:

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

Questo sostituisce il gestore della console con un FileHandler e significa ancora ottenere la registrazione ma non devo toccare la base di codice di produzione.


0

Se stai usando pytest :

Poiché pytest acquisisce i messaggi di registro e li visualizza solo per i test non riusciti, in genere non si desidera disabilitare alcuna registrazione. Invece, usa un settings.pyfile separato per i test (es., test_settings.py) E aggiungi ad esso:

LOGGING_CONFIG = None

Questo dice a Django di saltare completamente la configurazione della registrazione. IlLOGGING impostazione verrà ignorata e può essere rimossa dalle impostazioni.

Con questo approccio, non si ottiene alcuna registrazione per i test superati e si ottengono tutte le registrazioni disponibili per i test non riusciti.

I test verranno eseguiti utilizzando la registrazione impostata da pytest. Può essere configurato a proprio piacimento nelle pytestimpostazioni (ad es tox.ini.). Per includere i messaggi di registro a livello di debug, utilizzare log_level = DEBUG(o l'argomento della riga di comando corrispondente).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.