Bagaimana saya bisa menonaktifkan logging saat menjalankan tes unit di Python Django?

168

Saya menggunakan pelari tes berbasis unit test sederhana untuk menguji aplikasi Django saya.

Aplikasi saya sendiri dikonfigurasi untuk menggunakan logger dasar di settings.py menggunakan:

logging.basicConfig(level=logging.DEBUG)

Dan dalam kode aplikasi saya menggunakan:

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

Namun, saat menjalankan unittests, saya ingin menonaktifkan pencatatan agar tidak mengacaukan hasil pengujian saya. Apakah ada cara sederhana untuk mematikan proses masuk secara global, sehingga penebang khusus aplikasi tidak menulis hal-hal ke konsol ketika saya menjalankan tes?

shreddd
sumber
Bagaimana Anda mengaktifkan pencatatan log saat menjalankan tes? dan mengapa kamu tidak menggunakan LOGO django?
dalore

Jawaban:

249
logging.disable(logging.CRITICAL)

akan menonaktifkan semua panggilan logging dengan level yang tidak terlalu parah atau sama dengan CRITICAL. Logging dapat diaktifkan kembali dengan

logging.disable(logging.NOTSET)
unutbu
sumber
42
Ini mungkin jelas tetapi saya merasa bermanfaat untuk kadang-kadang menyatakan yang jelas untuk kepentingan pembaca lain: Anda akan menelepon logging.disable(dari jawaban yang diterima) di bagian atas tests.pydalam aplikasi Anda yang melakukan pencatatan.
CJ Gaconnet
7
Saya akhirnya menempatkan panggilan di setUp () tetapi poin Anda diterima dengan baik.
shreddd
dalam metode setUp () pengujian Anda, atau dalam pengujian sebenarnya yang menghasilkan pesan log yang ingin Anda sembunyikan.
qris
10
Dan dalam tearDown()metode Anda : logging.disable(logging.NOTSET)menempatkan logging kembali ke tempatnya dengan rapi.
mlissner
34
Meletakkannya di init .py dari testsmodul ini sangat berguna.
toabi
46

Karena Anda berada di Django, Anda dapat menambahkan baris ini ke settings.py Anda:

import sys
import logging

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

Dengan begitu Anda tidak perlu menambahkan baris itu di setiap setUp()tes Anda.

Anda juga dapat melakukan beberapa perubahan praktis untuk kebutuhan pengujian Anda dengan cara ini.

Ada cara "lebih baik" atau "bersih" lainnya untuk menambahkan spesifik ke tes Anda dan itu adalah membuat pelari tes Anda sendiri.

Cukup buat kelas seperti ini:

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)

Dan sekarang tambahkan ke file settings.py Anda:

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

Ini memungkinkan Anda melakukan satu modifikasi yang sangat berguna yang tidak dilakukan oleh pendekatan lain, yaitu membuat Django hanya menguji aplikasi yang Anda inginkan. Anda dapat melakukannya dengan mengubah test_labelsmenambahkan baris ini ke pelari uji:

if not test_labels:
    test_labels = ['my_app1', 'my_app2', ...]
Hassek
sumber
Tentu - memasukkannya ke dalam settings.py akan menjadikannya global.
shreddd
7
untuk Django 1.6+ silakan periksa jawaban @alukach.
Hassek
2
Terkadang dalam pengujian unit, saya ingin menegaskan bahwa kesalahan telah dicatat sehingga metode ini tidak ideal. Tetap saja, itu adalah jawaban yang bagus.
Sardathrion - melawan penyalahgunaan SE
23

Apakah ada cara sederhana untuk mematikan proses masuk secara global, sehingga penebang khusus aplikasi tidak menulis hal-hal ke konsol ketika saya menjalankan tes?

Jawaban lain mencegah "menulis hal-hal ke konsol" dengan secara global menetapkan infrastruktur logging untuk mengabaikan apa pun. Ini bekerja tetapi saya merasa terlalu tumpul pendekatan. Pendekatan saya adalah melakukan perubahan konfigurasi yang hanya melakukan apa yang diperlukan untuk mencegah log keluar di konsol. Jadi saya menambahkan filter pencatatan khusus ke 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

Dan saya mengkonfigurasi Django logging untuk menggunakan filter:

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

Hasil akhir: ketika saya menguji, tidak ada yang masuk ke konsol, tetapi semuanya tetap sama.

Kenapa melakukan ini?

Saya mendesain kode yang berisi instruksi logging yang dipicu hanya dalam keadaan tertentu dan yang akan menghasilkan data yang tepat yang saya butuhkan untuk diagnosis jika ada kesalahan. Oleh karena itu saya menguji bahwa mereka melakukan apa yang seharusnya mereka lakukan dan dengan demikian sepenuhnya menonaktifkan logging tidak layak bagi saya. Saya tidak ingin menemukan begitu perangkat lunak dalam produksi bahwa apa yang saya pikir akan dicatat tidak dicatat.

Selain itu, beberapa pelari uji (Hidung, misalnya) akan menangkap log selama pengujian dan output bagian yang relevan dari log bersama dengan kegagalan pengujian. Berguna untuk mencari tahu mengapa suatu tes gagal. Jika logging sepenuhnya dimatikan, maka tidak ada yang bisa ditangkap.

Louis
sumber
"Semua pelari ujian yang saya gunakan akan memuat kode Django dengan cara yang membuatnya Benar." Menarik ... Bagaimana?
webtweakers
Saya memiliki test_settings.pyfile yang berada di sebelah proyek saya settings.py. Sudah diatur untuk memuat settings.pydan membuat beberapa perubahan seperti diatur TESTING_MODEke True. Pelari ujian saya diatur sehingga test_settingsmodul dimuat untuk pengaturan proyek Django. Ada banyak cara yang bisa dilakukan. Saya biasanya pergi dengan mengatur variabel lingkungan DJANGO_SETTINGS_MODULEke proj.test_settings.
Louis
Ini luar biasa dan melakukan persis apa yang saya inginkan. Menyembunyikan logging selama unittests sampai sesuatu gagal - kemudian Django Nose mengambil output dan mencetaknya dengan kegagalan. Sempurna. Gabungkan dengan ini untuk menentukan apakah pengujian unit aktif.
rrauenza
21

Saya menyukai ide pelari uji coba Hassek. Perlu dicatat bahwa DjangoTestSuiteRunnertidak ada lagi runner tes default di Django 1.6+, telah digantikan oleh DiscoverRunner. Untuk perilaku default, pelari ujian harus lebih seperti:

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)
alukach
sumber
Saya menemukan solusi Anda setelah mencoba banyak hal. Namun saya tidak dapat mengatur variabel TEST_RUNNER dalam pengaturan karena tidak dapat mengimpor modul tempat file test_runner berada.
Kelinci Kelinci
Kedengarannya seperti masalah impor. Apakah Anda mengatur TEST_RUNNER ke jalur string ke pelari (bukan modul Python yang sebenarnya)? Juga, di mana pelari Anda berada? Saya memiliki tambang di aplikasi terpisah bernama helpers, yang hanya memiliki utilitas yang tidak mengimpor dari tempat lain di dalam proyek.
alukach
5

Saya telah menemukan bahwa untuk pengujian dalam unittestatau sejenisnya kerangka kerja, cara paling efektif untuk menonaktifkan dengan aman masuk logging unit yang tidak diinginkan adalah untuk mengaktifkan / menonaktifkan dalam setUp/ tearDownmetode kasus uji tertentu. Ini memungkinkan satu target khusus di mana log harus dinonaktifkan. Anda juga bisa melakukan ini secara eksplisit di logger dari kelas yang Anda uji.

import unittest
import logging

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

    def tearDown(self):
        logging.disable(logging.NOTSET)
mcguip
sumber
4

Saya menggunakan dekorator metode sederhana untuk menonaktifkan logging hanya dalam metode pengujian tertentu.

def disable_logging(f):

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

        return result

    return wrapper

Dan kemudian saya menggunakannya seperti dalam contoh berikut:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass
Eduard Mukans
sumber
3

Ada beberapa metode yang cantik dan bersih untuk menangguhkan proses masuk dengan unittest.mock.patchmetode tes .

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

Dan tidak python3 -m unittest testsakan menghasilkan keluaran logging.

valex
sumber
1

Terkadang Anda ingin log dan terkadang tidak. Saya memiliki kode ini disettings.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)

Jadi, jika Anda menjalankan tes dengan --no-logsopsi, Anda hanya akan mendapatkan criticallog:

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

Ini sangat membantu jika Anda ingin mempercepat tes pada aliran integrasi berkelanjutan Anda.

Karim N Gorjux
sumber
1

Jika Anda tidak ingin itu berkali-kali nyalakan / matikan di setUp () dan tearDown () untuk unittest (tidak melihat alasan untuk itu), Anda bisa melakukannya sekali per kelas:

    import unittest
    import logging

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

Dalam kasus di mana saya ingin sementara waktu menekan logger tertentu, saya telah menulis manajer konteks kecil yang saya temukan berguna:

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

Anda kemudian menggunakannya seperti:

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

Ini memiliki keuntungan bahwa logger diaktifkan kembali (atau diatur kembali ke keadaan sebelumnya) setelah withselesai.

Nathan Villaescusa
sumber
1

Anda dapat menempatkan ini di direktori tingkat atas untuk __init__.pyfile unit test . Ini akan menonaktifkan logging secara global di unit test suite.

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)
Aaron Lelevier
sumber
0

Dalam kasus saya, saya memiliki file pengaturan yang settings/test.pydibuat khusus untuk tujuan pengujian, begini tampilannya:

from .base import *

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

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

LOGGING = {}

Saya menempatkan variabel lingkungan DJANGO_SETTINGS_MODULE=settings.testke /etc/environment.

Dmitrii Mikhailov
sumber
0

Jika Anda memiliki modul initaliser yang berbeda untuk pengujian, pengembangan, dan produksi, maka Anda dapat menonaktifkan apa pun atau mengalihkannya di server awal. Saya memiliki local.py, test.py dan production.py yang semuanya mewarisi dari common.y

common.py melakukan semua konfigurasi utama termasuk cuplikan ini:

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

Kemudian di test.py saya punya ini:

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

Ini menggantikan pengendali konsol dengan FileHandler dan berarti masih mendapatkan logging tapi saya tidak harus menyentuh basis kode produksi.

Christopher Broderick
sumber
0

Jika Anda menggunakan pytest:

Karena pytest menangkap pesan log dan hanya menampilkannya untuk pengujian yang gagal, Anda biasanya tidak ingin menonaktifkan logging. Alih-alih, gunakan settings.pyfile terpisah untuk pengujian (mis., test_settings.py), Dan tambahkan ke dalamnya:

LOGGING_CONFIG = None

Ini memberitahu Django untuk melewatkan mengkonfigurasi logging sama sekali. ItuLOGGING pengaturan akan diabaikan dan dapat dihapus dari pengaturan.

Dengan pendekatan ini, Anda tidak mendapatkan logging untuk tes yang lulus, dan Anda mendapatkan semua logging untuk tes yang gagal.

Tes akan berjalan menggunakan pencatatan yang diatur oleh pytest. Ini dapat dikonfigurasi sesuai dengan keinginan Anda di pytestpengaturan (misalnya, tox.ini). Untuk memasukkan pesan log level debug, gunakan log_level = DEBUG(atau argumen baris perintah yang sesuai).

Roger Dahl
sumber