wake-up-neo.com

Wie kann ich die Protokollierung deaktivieren, während Komponententests in Python Django ausgeführt werden?

Ich verwende einen einfachen Testläufer für Gerätetests, um meine Django-Anwendung zu testen.

Meine Anwendung selbst ist so konfiguriert, dass sie einen einfachen Logger in settings.py verwendet.

logging.basicConfig(level=logging.DEBUG)

Und in meinem Anwendungscode mit:

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

Beim Ausführen von unittests möchte ich jedoch die Protokollierung deaktivieren, damit meine Testergebnisausgabe nicht durcheinander gerät. Gibt es eine einfache Möglichkeit, die Protokollierung auf globale Weise zu deaktivieren, sodass die anwendungsspezifischen Logger beim Ausführen von Tests keine Daten in die Konsole schreiben?

138
shreddd
logging.disable(logging.CRITICAL)

deaktiviert alle Protokollierungsaufrufe mit weniger oder weniger gleichwertigen Werten wie CRITICAL. Die Protokollierung kann mit erneut aktiviert werden

logging.disable(logging.NOTSET)
211
unutbu

Da Sie sich in Django befinden, können Sie diese Einstellungen zu Ihren Einstellungen hinzufügen.py:

import sys
import logging

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

Auf diese Weise müssen Sie diese Zeile nicht in jedem setUp () Ihrer Tests hinzufügen. :)

Auf diese Weise könnten Sie auch einige praktische Änderungen für Ihre Testanforderungen vornehmen.

Es gibt noch eine weitere "schönere" oder "sauberere" Methode, um Ihren Tests bestimmte Merkmale hinzuzufügen.

Erstellen Sie einfach eine Klasse wie diese:

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)

Und füge jetzt zu deiner Datei settings.py hinzu:

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

Auf diese Weise können Sie eine wirklich praktische Änderung vornehmen, die der andere Ansatz nicht tut. Django testet nur die gewünschten Anwendungen. Sie können dies tun, indem Sie die test_labels ändern und diese Zeile dem Testläufer hinzufügen:

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

Gibt es eine einfache Möglichkeit, die Protokollierung global zu deaktivieren, damit die anwendungsspezifischen Protokollierer beim Ausführen von Tests keine Daten in die Konsole schreiben?

Die anderen Antworten verhindern das "Ausgeben von Inhalten an die Konsole", indem die Protokollierungsinfrastruktur global so eingestellt wird, dass alles ignoriert wird. Das funktioniert, aber ich finde es zu stumpf. Mein Ansatz ist es, eine Konfigurationsänderung durchzuführen, die nur das tut, was erforderlich ist, um zu verhindern, dass Protokolle auf die Konsole gelangen. Deshalb füge ich meinem settings.py Ein benutzerdefiniertes Protokollierungsfilter hinzu:

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

Und ich konfiguriere das Django logging um den Filter zu benutzen:

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

Endergebnis: Beim Testen wird nichts an die Konsole gesendet, aber alles andere bleibt gleich.

Warum das machen?

Ich entwerfe Code, der Protokollierungsanweisungen enthält, die nur unter bestimmten Umständen ausgelöst werden und die genauen Daten ausgeben sollten, die ich für die Diagnose benötige, wenn etwas schief geht. Daher teste ich , dass sie das tun, was sie tun sollen, und daher ist es für mich nicht sinnvoll, die Protokollierung vollständig zu deaktivieren. Ich möchte nicht feststellen, dass, wenn die Software in Produktion ist, das, was ich dachte , nicht protokolliert wird.

Darüber hinaus erfassen einige Testläufer (z. B. Nose) während des Tests Protokolle und geben den relevanten Teil des Protokolls zusammen mit einem Testfehler aus. Dies ist hilfreich, um herauszufinden, warum ein Test fehlgeschlagen ist. Wenn die Protokollierung vollständig deaktiviert ist, kann nichts erfasst werden.

21
Louis

Ich mag die individuelle Testläufer-Idee von Hassek. Es ist zu beachten, dass DjangoTestSuiteRunner nicht mehr der Standard-Testläufer in Django 1.6+ ist. Er wurde durch DiscoverRunner ersetzt. Für das Standardverhalten sollte der Testläufer wie folgt aussehen:

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)
20
alukach

Es gibt eine hübsche und saubere Methode, um die Protokollierung in Tests mit der unittest.mock.patch-Methode auszusetzen.

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

Und python3 -m unittest tests erzeugt keine Protokollierungsausgabe.

2
valex

Ich habe festgestellt, dass für Tests in unittest oder einem ähnlichen Framework die effektivste Methode zum sicheren Deaktivieren der unerwünschten Protokollierung in Komponententests das Aktivieren/Deaktivieren der Methoden setUp/tearDown eines bestimmten Testfalls ist. Dadurch kann ein Ziel gezielt festgelegt werden, wo Protokolle deaktiviert werden sollen. Sie können dies auch explizit mit dem Logger der Klasse tun, die Sie testen.

import unittest
import logging

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

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

Ich verwende einen einfachen Methodendekorator, um die Protokollierung nur für eine bestimmte Testmethode zu deaktivieren.

def disable_logging(f):

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

        return result

    return wrapper

Und dann benutze ich es wie im folgenden Beispiel:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass
1
Eduard Mukans

Manchmal möchten Sie die Protokolle und manchmal nicht. Ich habe diesen Code in meinem 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)

Wenn Sie also Ihren Test mit den --no-logs-Optionen ausführen, erhalten Sie nur die critical-Protokolle:

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

Dies ist sehr hilfreich, wenn Sie die Tests für Ihren kontinuierlichen Integrationsfluss beschleunigen möchten.

1
Karim N Gorjux

Wenn Sie verschiedene Initalisierermodule für Test, Entwicklung und Produktion haben, können Sie alles deaktivieren oder im Initialisierer umleiten. Ich habe local.py, test.py und production.py, die alle von common.y erben

common.py führt die gesamte Hauptkonfiguration aus, einschließlich dieses Snippets:

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

Dann habe ich in test.py folgendes:

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

Dies ersetzt den Konsolen-Handler durch einen FileHandler und bedeutet, dass immer noch eine Protokollierung durchgeführt wird, aber ich muss die Produktionscode-Basis nicht berühren.

Wenn Sie pytest verwenden:

Da pytest Protokollnachrichten erfasst und nur für fehlgeschlagene Tests anzeigt, möchten Sie normalerweise keine Protokollierung deaktivieren. Verwenden Sie stattdessen eine separate settings.py -Datei für Tests (z. B. test_settings.py), und fügen Sie Folgendes hinzu:

LOGGING_CONFIG = None

Dies weist Django an, die Konfiguration der Protokollierung insgesamt zu überspringen. Die Einstellung LOGGING wird ignoriert und kann aus den Einstellungen entfernt werden.

Mit diesem Ansatz erhalten Sie keine Protokollierung für bestandene Tests und alle verfügbaren Protokollierungen für fehlgeschlagene Tests.

Die Tests werden mit der Protokollierung ausgeführt, die von pytest eingerichtet wurde. Es kann nach Ihren Wünschen in den pytest Einstellungen konfiguriert werden (z. B. tox.ini). Verwenden Sie log_level = DEBUG (oder das entsprechende Befehlszeilenargument), um Protokollnachrichten auf Debug-Ebene einzuschließen.

0
Roger Dahl

Wenn Sie nicht möchten, dass es wiederholt in setUp () und tearDown () für unittest ein-/ausgeschaltet wird (sehen Sie nicht den Grund dafür), können Sie es nur einmal pro Klasse tun:

    import unittest
    import logging

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

In meinem Fall habe ich eine Einstellungsdatei settings/test.py, die speziell für Testzwecke erstellt wurde. So sieht sie aus:

from .base import *

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

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

LOGGING = {}

Ich habe eine Umgebungsvariable Django_SETTINGS_MODULE=settings.test in /etc/environment gesetzt.

0