Python Django'da birim testleri çalıştırırken günlüğe kaydetmeyi nasıl devre dışı bırakabilirim?


168

Django uygulamamı test etmek için basit bir birim test tabanlı test çalıştırıcısı kullanıyorum.

Uygulamamın kendisi, settings.py dosyasında temel bir günlükçü kullanacak şekilde yapılandırıldı:

logging.basicConfig(level=logging.DEBUG)

Ve benim uygulama kodu kullanarak:

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

Ancak, unittests çalıştırırken, test sonucu çıktımı engellememesi için günlüğe kaydetmeyi devre dışı bırakmak istiyorum. Günlük çalıştırmayı kapatmanın basit bir yolu var mı, böylece testleri çalıştırdığımda uygulamaya özgü kaydediciler konsola bir şeyler yazmayacak mı?


Testler yapılırken günlüğe kaydetmeyi nasıl etkinleştirdiniz? ve neden django LOGGING kullanmıyorsunuz?
dalore

Yanıtlar:


249
logging.disable(logging.CRITICAL)

seviyeleri daha düşük veya eşit olan tüm günlük aramalarını devre dışı bırakır CRITICAL. Günlük kaydı aşağıdakilerle yeniden etkinleştirilebilir:

logging.disable(logging.NOTSET)

42
Bu açık olabilir, ancak bazen diğer okuyucuların yararına bariz olarak belirtmeyi yararlı buluyorum: Günlük kaydını yapan başvurunuzun logging.disableüst kısmına (kabul edilen cevaptan) çağrı yaparsınız tests.py.
CJ Gaconnet

7
SetUp () çağrı koymak sona erdi ama nokta iyi alınır.
shreddd

testinizin setUp () yönteminde veya gizlemek istediğiniz günlük iletilerini oluşturan gerçek testte.
qris

10
Ve tearDown()yönteminizde: logging.disable(logging.NOTSET)günlüğe kaydetmeyi düzgün bir şekilde yerine koyar.
mlissner

34
Modülün init .py'sine koymak testsçok faydalıdır.
toabi

46

Django'da olduğunuz için bu satırları ayarlarınıza ekleyebilirsiniz.

import sys
import logging

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

Bu şekilde setUp()testlerinize bu satırı eklemenize gerek kalmaz .

Ayrıca test ihtiyaçlarınız için bu şekilde birkaç kullanışlı değişiklik yapabilirsiniz.

Testlerinize ayrıntılar eklemenin başka bir "daha hoş" veya "daha temiz" yolu var ve bu da kendi test koşucunuzu oluşturuyor.

Sadece böyle bir sınıf oluşturun:

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)

Ve şimdi settings.py dosyanıza ekleyin:

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

Bu, diğer yaklaşımın yapmadığı gerçekten kullanışlı bir değişiklik yapmanıza izin verir, bu da Django'nun sadece istediğiniz uygulamaları test etmesini sağlar. Bunu test_labels, test satırına bu satırı ekleyerek değiştirerek yapabilirsiniz :

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

Elbette - settings.py'ye koymak onu global hale getirir.
shreddd

7
Django 1.6+ için lütfen @alukach cevabını kontrol edin.
Hassek

2
Bazen birim testlerde, bir hatanın günlüğe kaydedildiğini iddia etmek istiyorum, bu nedenle bu yöntem ideal değildir. Yine de, bir iyi bir cevap.
Sardathrion - SE kötüye karşı

23

Günlük çalıştırmayı kapatmanın basit bir yolu var mı, böylece testleri çalıştırdığımda uygulamaya özgü kaydediciler konsola bir şeyler yazmayacak mı?

Diğer yanıtlar, günlük kaydı altyapısını herhangi bir şeyi göz ardı edecek şekilde global olarak ayarlayarak "konsola yazı yazmayı" önler. Bu işe yarıyor ama ben bu yaklaşımı çok açık buluyorum. Benim yaklaşımım, yalnızca günlüklerin konsoldan çıkmasını önlemek için gerekenleri yapan bir yapılandırma değişikliği yapmaktır. Bir ekleme Yani özel günlüğü filtresi kardeşime karşı 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

Ve Django günlüğü yapılandırmak filtre kullanmak için:

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

Sonuç: Test ederken, hiçbir şey konsola gitmez, ancak diğer her şey aynı kalır.

Neden Yapmalı?

Yalnızca belirli durumlarda tetiklenen ve işler ters giderse tanı için ihtiyacım olan kesin verileri vermesi gereken günlük kaydı talimatlarını içeren bir kod tasarlarım. Bu yüzden yapmaları gerekeni yaptıklarını test ediyorum ve bu nedenle günlüğe kaydetmeyi tamamen devre dışı bırakmak benim için uygun değil. Yazılım üretildikten sonra günlüğe kaydedileceğini düşündüğüm şeyin günlüğe kaydedilmediğini bulmak istemiyorum .

Dahası, bazı test koşucuları (örneğin Burun) test sırasında günlükleri yakalar ve bir test hatasıyla birlikte günlüğün ilgili kısmını çıkarır. Bir testin neden başarısız olduğunu bulmakta kullanışlıdır. Günlük kaydı tamamen kapalıysa, yakalanabilecek hiçbir şey yoktur.


"Kullandığım herhangi bir test çalıştırıcısı, Django kodunu True yapacak şekilde yükleyecektir." İlginç ... Nasıl?
Mart'ta webtweakers

test_settings.pyProjemin yanında oturan bir dosyam var settings.py. Yüklenecek şekilde ayarlanır settings.pyve seti gibi bazı değişiklikler yapmak TESTING_MODEiçin True. Test koşucularım test_settingsDjango proje ayarları için yüklenen modül olacak şekilde düzenlenmiştir . Bunun birçok yolu vardır. Genellikle ortam değişkenini DJANGO_SETTINGS_MODULEolarak ayarlamaya giderim proj.test_settings.
Louis

Bu harika ve tam olarak istediğimi yapıyor. Bir şey başarısız olana kadar birim testleri sırasında günlüğü gizler - ardından Django Nose çıktıyı alır ve başarısızlıkla yazdırır. Mükemmel. İle birleştirin bu birim testleri etkin olup olmadığını belirlemek için.
rrauenza

21

Hassek'in özel test koşucusu fikrini seviyorum. Artık DjangoTestSuiteRunnerDjango 1.6+ sürümünde varsayılan test koşucusu olmadığına, bunun yerine DiscoverRunner. Varsayılan davranış için, test çalıştırıcısı daha çok şöyle olmalıdır:

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)

Bir çok şeyi denedikten sonra çözümünüzü buldum. Ancak, test_runner dosyasının bulunduğu modülü alamadığı için TEST_RUNNER değişkenini ayarlarda ayarlayamıyorum.
Tavşan Tavşan

Bir ithalat sorunu gibi görünüyor. TEST_RUNNER'ı koşucunun dize yoluna mı ayarlıyorsunuz (gerçek Python modülü değil)? Ayrıca koşucunuz nerede? Benim adıma ayrı bir uygulama var helpers, bu sadece proje içinde başka bir yerden ithal etmeyen araçlar var.
alukach

5

unittestBir çerçevedeki veya benzer bir çerçevedeki testler için, birim testlerinde istenmeyen günlüğü güvenli bir şekilde devre dışı bırakmanın en etkili yolunun, belirli bir test durumunun setUp/ tearDownyöntemlerinde etkinleştirilmesi / devre dışı bırakılması olduğunu buldum . Bu, özellikle günlüklerin devre dışı bırakılması gereken bir hedefe izin verir. Bunu, test ettiğiniz sınıfın kayıt cihazında da açıkça yapabilirsiniz.

import unittest
import logging

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

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

4

Yalnızca belirli bir test yönteminde günlüğe kaydetmeyi devre dışı bırakmak için basit bir yöntem dekoratör kullanıyorum.

def disable_logging(f):

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

        return result

    return wrapper

Ve sonra aşağıdaki örnekte olduğu gibi kullanıyorum:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass

3

Yöntemlerle testlerde günlüğe kaydetmeyi askıya almak için güzel ve temiz bir unittest.mock.patchyöntem vardır.

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

Ve python3 -m unittest testshiçbir günlük çıkışı üretmez.


1

Bazen günlükleri istersiniz bazen de istemezsiniz. Bu kodu benimsettings.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)

Bu nedenle, testinizi --no-logsseçeneklerle çalıştırırsanız yalnızca criticalgünlükleri alırsınız :

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

Sürekli entegrasyon akışınızdaki testleri hızlandırmak istiyorsanız çok faydalıdır.


1

Unittest için setUp () ve tearDown () 'da tekrar tekrar açıp kapatmak istemiyorsanız (bunun nedenini görmüyorsanız), sınıf başına sadece bir kez yapabilirsiniz:

    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

Belirli bir kaydediciyi geçici olarak bastırmak istediğim durumlarda, yararlı bulduğum küçük bir içerik yöneticisi yazdım:

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

Daha sonra şöyle kullanırsınız:

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

Bunun avantajı, tamamlandıktan sonra kaydedicinin yeniden etkinleştirilmesidir (veya önceki durumuna geri döndürülmesidir) with.


1

Birim testleri __init__.pydosyası için bunu üst düzey dizine koyabilirsiniz . Bu, birim test paketinde genel olarak günlüğe kaydetmeyi devre dışı bırakır.

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

0

Benim durumumda settings/test.py, test amaçları için özel olarak oluşturulmuş bir ayarlar dosyası var , işte şöyle görünüyor:

from .base import *

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

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

LOGGING = {}

Bir ortam değişkeni koydu DJANGO_SETTINGS_MODULE=settings.testiçin /etc/environment.


0

Test, geliştirme ve üretim için farklı initaliser modülleriniz varsa, herhangi bir şeyi devre dışı bırakabilir veya başlatıcıda yeniden yönlendirebilirsiniz. Hepsinden ortak olan dev.y, test.py ve production.py var.

common.py bu pasaj dahil tüm ana yapılandırmaları yapar:

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

Sonra test.py bu var:

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

Bu, konsol işleyicisini bir FileHandler ile değiştirir ve yine de günlüğe kaydetme anlamına gelir, ancak üretim kodu tabanına dokunmak zorunda değilim.


0

Kullanıyorsanız pytest:

Pytest günlük iletilerini yakaladığından ve yalnızca başarısız sınamalar için görüntülediğinden, genellikle hiçbir günlüğü devre dışı bırakmak istemezsiniz. Bunun yerine, settings.pytestler için ayrı bir dosya kullanın (örn. test_settings.py) Ve buna ekleyin:

LOGGING_CONFIG = None

Bu, Django'ya günlük kaydını tamamen yapılandırmayı atlamasını söyler. LOGGINGAyar yok sayılacak ve ayarlarından kaldırılabilir.

Bu yaklaşımla, başarılı testler için herhangi bir günlük kaydı almazsınız ve başarısız testler için tüm günlük kaydını alırsınız.

Testler, tarafından ayarlanan günlük kaydı kullanılarak çalıştırılır pytest. pytestAyarlardan istediğiniz gibi yapılandırılabilir (örn tox.ini.). Hata ayıklama düzeyi günlük iletileri eklemek için log_level = DEBUG(veya karşılık gelen komut satırı bağımsız değişkenini) kullanın.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.