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?
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)
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', ...]
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.
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.
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)
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.
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)
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
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.
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.
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)
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.