ฉันจะปิดการใช้งานการบันทึกในขณะที่ทำการทดสอบหน่วยใน Python Django ได้อย่างไร


168

ฉันใช้นักทดสอบทดสอบพื้นฐานเพื่อทดสอบแอปพลิเคชัน Django ของฉัน

แอปพลิเคชันของฉันถูกกำหนดค่าให้ใช้ตัวบันทึกพื้นฐานใน settings.py โดยใช้:

logging.basicConfig(level=logging.DEBUG)

และในรหัสแอปพลิเคชันของฉันโดยใช้:

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

อย่างไรก็ตามเมื่อเรียกใช้ unittests ฉันต้องการปิดใช้งานการบันทึกเพื่อไม่ให้ส่งผลการทดสอบของฉันยุ่งเหยิง มีวิธีง่าย ๆ ในการปิดการบันทึกในลักษณะสากลหรือไม่เพื่อให้โปรแกรมบันทึกเฉพาะแอปพลิเคชันไม่ได้เขียนเนื้อหาลงในคอนโซลเมื่อฉันทำการทดสอบ


คุณเปิดใช้งานการบันทึกขณะใช้การทดสอบอย่างไร และทำไมคุณไม่ใช้ django ในการเข้าสู่ระบบ?
dalore

คำตอบ:


249
logging.disable(logging.CRITICAL)

CRITICALจะปิดการใช้งานทุกสายการเข้าสู่ระบบที่มีระดับความรุนแรงน้อยกว่าหรือเท่ากับ การบันทึกสามารถเปิดใช้งานอีกครั้งด้วย

logging.disable(logging.NOTSET)

42
สิ่งนี้อาจเห็นได้ชัด แต่ฉันคิดว่ามันมีประโยชน์ที่บางครั้งจะระบุสิ่งที่ชัดเจนเพื่อประโยชน์ของผู้อ่านรายอื่น: คุณจะโทรไปที่logging.disable(จากคำตอบที่ยอมรับ) ที่ด้านบนของtests.pyแอปพลิเคชันของคุณที่ทำการบันทึก
CJ Gaconnet

7
ฉันลงเอยด้วยการวางสายใน setUp () แต่ประเด็นของคุณดีพอ
shreddd

ในเมธอด setUp () ของการทดสอบของคุณหรือในการทดสอบจริงที่สร้างข้อความบันทึกที่คุณต้องการซ่อน
qris

10
และในtearDown()วิธีการของคุณ: logging.disable(logging.NOTSET)ทำการล็อกกลับเข้าที่อย่างเรียบร้อย
mlissner

34
การวางไว้ในinit .py ของtestsโมดูลมีประโยชน์มาก
toabi

46

เนื่องจากคุณอยู่ใน Django คุณสามารถเพิ่มบรรทัดเหล่านี้ใน settings.py:

import sys
import logging

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

ด้วยวิธีนี้คุณไม่ต้องเพิ่มบรรทัดนั้นในทุก ๆsetUp()การทดสอบของคุณ

คุณสามารถทำการเปลี่ยนแปลงที่มีประโยชน์สองสามอย่างสำหรับการทดสอบของคุณด้วยวิธีนี้

มีวิธี "ดีกว่า" หรือ "สะอาด" อีกวิธีหนึ่งในการเพิ่มรายละเอียดในการทดสอบของคุณและทำให้นักทดสอบของคุณเอง

เพียงสร้างคลาสเช่นนี้

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)

และตอนนี้เพิ่มไฟล์ settings.py ของคุณ:

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

วิธีนี้ช่วยให้คุณทำการปรับเปลี่ยนที่มีประโยชน์อย่างแท้จริงซึ่งวิธีอื่นไม่ได้ทำซึ่งทำให้ Django เป็นเพียงการทดสอบแอปพลิเคชันที่คุณต้องการ คุณสามารถทำได้โดยเปลี่ยนการtest_labelsเพิ่มบรรทัดนี้ไปยังนักทดสอบ

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

แน่นอน - การวางไว้ใน settings.py จะทำให้เป็นสากล
shreddd

7
สำหรับ Django 1.6+ โปรดตรวจสอบ @alukach คำตอบ
Hassek

2
บางครั้งในการทดสอบหน่วยฉันต้องการยืนยันว่ามีการบันทึกข้อผิดพลาดดังนั้นวิธีนี้จึงไม่เหมาะ ยังเป็นคำตอบที่ดี
Sardathrion - ต่อต้านการละเมิดใน

23

มีวิธีง่าย ๆ ในการปิดการบันทึกในลักษณะสากลหรือไม่เพื่อให้โปรแกรมบันทึกเฉพาะแอปพลิเคชันไม่ได้เขียนเนื้อหาลงในคอนโซลเมื่อฉันทำการทดสอบ

คำตอบอื่น ๆ ป้องกัน "การเขียนข้อมูลลงในคอนโซล" โดยการตั้งค่าโครงสร้างพื้นฐานการบันทึกเพื่อละเว้นสิ่งใด มันใช้งานได้ แต่ฉันพบว่ามันทื่อเกินไป แนวทางของฉันคือการเปลี่ยนแปลงการกำหนดค่าซึ่งทำในสิ่งที่จำเป็นเท่านั้นเพื่อป้องกันไม่ให้บันทึกออกไปบนคอนโซล ดังนั้นฉันจึงเพิ่มตัวกรองการบันทึกที่กำหนดเองใน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

และฉันกำหนดค่าการบันทึก Djangoเพื่อใช้ตัวกรอง:

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

ผลลัพธ์สุดท้าย: เมื่อฉันทดสอบไม่มีอะไรไปที่คอนโซล แต่ทุกอย่างยังคงเหมือนเดิม

ทำไมต้องทำเช่นนี้?

ฉันออกแบบรหัสที่มีคำแนะนำในการบันทึกที่ถูกเรียกใช้เฉพาะในสถานการณ์ที่เฉพาะเจาะจงและควรส่งออกข้อมูลที่แน่นอนที่ฉันต้องการสำหรับการวินิจฉัยหากมีสิ่งผิดปกติ ดังนั้นฉันจึงทดสอบว่าพวกเขาทำในสิ่งที่ควรทำและการปิดใช้งานการบันทึกอย่างสมบูรณ์นั้นไม่สามารถใช้งานได้สำหรับฉัน ฉันไม่ต้องการค้นหาเมื่อมีการใช้งานซอฟต์แวร์ซึ่งสิ่งที่ฉันคิดว่าจะถูกบันทึกไว้จะไม่ถูกบันทึกไว้

นอกจากนี้นักวิ่งทดสอบ (เช่นจมูก) จะจับบันทึกในระหว่างการทดสอบและส่งออกส่วนที่เกี่ยวข้องของบันทึกพร้อมกับความล้มเหลวในการทดสอบ มันมีประโยชน์ในการหาสาเหตุที่การทดสอบล้มเหลว หากการบันทึกถูกปิดอย่างสมบูรณ์แสดงว่าไม่มีสิ่งใดที่สามารถจับภาพได้


"นักวิ่งทดสอบที่ฉันใช้จะโหลดรหัส Django ในลักษณะที่ทำให้เป็นจริง" ที่น่าสนใจ ... อย่างไร
webtweakers

ฉันมีไฟล์ซึ่งตั้งอยู่ติดกับโครงการของฉันtest_settings.py settings.pyมันถูกกำหนดให้โหลดsettings.pyและทำการเปลี่ยนแปลงบางอย่างเช่นชุดไปTESTING_MODE Trueนักวิ่งทดสอบของฉันได้รับการจัดระเบียบเพื่อที่test_settingsจะโหลดโมดูลสำหรับการตั้งค่าโครงการ Django มีหลายวิธีที่สามารถทำได้ ฉันมักจะไปกับการตั้งค่าตัวแปรสภาพแวดล้อมในการDJANGO_SETTINGS_MODULE proj.test_settings
Louis

มันยอดเยี่ยมและทำในสิ่งที่ฉันต้องการ ซ่อนการบันทึกในระหว่าง unittests จนกว่าจะมีบางอย่างล้มเหลว - จากนั้น Django Nose จะรับเอาต์พุตและพิมพ์ด้วยความล้มเหลว สมบูรณ์ ใช้ร่วมกับ สิ่งนี้เพื่อพิจารณาว่าการทดสอบหน่วยทำงานอยู่หรือไม่
ruuenza

21

ฉันชอบแนวคิดการทดสอบแบบกำหนดเองของ Hassek มันควรจะตั้งข้อสังเกตว่าDjangoTestSuiteRunnerไม่มีนักวิ่งเริ่มต้นการทดสอบใน Django 1.6 + DiscoverRunnerจะได้รับการแทนที่ด้วย สำหรับพฤติกรรมเริ่มต้นผู้ทดสอบควรมีลักษณะดังนี้:

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)

ฉันพบวิธีแก้ปัญหาของคุณหลังจากลองสิ่งต่างๆมากมาย อย่างไรก็ตามฉันไม่สามารถตั้งค่าตัวแปร TEST_RUNNER ในการตั้งค่าเนื่องจากไม่สามารถนำเข้าโมดูลที่ไฟล์ test_runner อยู่
Bunny Rabbit

เสียงเหมือนปัญหาการนำเข้า คุณตั้งค่า TEST_RUNNER เป็นเส้นทางสตริงไปยังนักวิ่ง (ไม่ใช่โมดูล Python จริง) หรือไม่? นอกจากนี้นักวิ่งของคุณอยู่ที่ไหน? ฉันมีชื่อของฉันในแอปแยกต่างหากhelpersซึ่งมีเพียงสิ่งที่ไม่นำเข้าจากที่อื่นภายในโครงการ
alukach

5

ฉันพบว่าสำหรับการทดสอบภายในunittestหรือคล้ายกับกรอบวิธีที่มีประสิทธิภาพที่สุดในการปิดการใช้งานการเข้าสู่ระบบที่ไม่พึงประสงค์อย่างปลอดภัยในการทดสอบหน่วยคือการเปิด / ปิดการใช้งานในsetUp/ tearDownวิธีการของกรณีทดสอบเฉพาะ สิ่งนี้จะช่วยให้หนึ่งเป้าหมายโดยเฉพาะที่ควรปิดการใช้งานบันทึก คุณสามารถทำสิ่งนี้อย่างชัดเจนกับคนตัดไม้ของชั้นเรียนที่คุณกำลังทดสอบ

import unittest
import logging

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

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

4

ฉันใช้เครื่องมือตกแต่งวิธีการง่ายๆในการปิดใช้งานการบันทึกในวิธีการทดสอบเฉพาะเท่านั้น

def disable_logging(f):

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

        return result

    return wrapper

แล้วฉันจะใช้มันในตัวอย่างต่อไปนี้:

class ScenarioTestCase(TestCase):

    @disable_logging
    test_scenario(self):
        pass

3

มีวิธีการที่สวยและสะอาดในการระงับการเข้าสู่ระบบในการทดสอบด้วยunittest.mock.patchวิธีการ

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

และpython3 -m unittest testsจะไม่มีการบันทึกผลลัพธ์


1

บางครั้งคุณต้องการบันทึกและบางครั้งไม่ ฉันมีรหัสนี้ในของฉัน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)

ดังนั้นหากคุณทำการทดสอบด้วย--no-logsตัวเลือกคุณจะได้รับเฉพาะcriticalบันทึก:

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

มันมีประโยชน์มากถ้าคุณต้องการเพิ่มความเร็วการทดสอบในโฟลว์การรวมต่อเนื่องของคุณ


1

หากคุณไม่ต้องการให้เปิด / ปิดซ้ำ ๆ ใน setUp () และ tearDown () สำหรับ unittest (ไม่เห็นเหตุผลในเรื่องนั้น) คุณสามารถทำได้เพียงครั้งเดียวต่อคลาส:

    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

ในกรณีที่ฉันต้องการระงับการบันทึกชั่วคราวฉันได้เขียนผู้จัดการบริบทเล็กน้อยที่ฉันพบว่ามีประโยชน์:

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

จากนั้นคุณใช้มันเช่น:

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

นี่เป็นข้อได้เปรียบที่คนตัดไม้เปิดใช้งานอีกครั้ง (หรือตั้งค่ากลับสู่สถานะเดิม) เมื่อwithเสร็จสิ้น


1

คุณสามารถวางสิ่งนี้ไว้ในไดเรกทอรีระดับบนสุดสำหรับ__init__.pyไฟล์ทดสอบหน่วย สิ่งนี้จะปิดใช้งานการบันทึกทั่วโลกในชุดทดสอบหน่วย

# tests/unit/__init__.py
import logging

logging.disable(logging.CRITICAL)

0

ในกรณีของฉันฉันมีไฟล์การตั้งค่าที่ settings/test.pyสร้างขึ้นโดยเฉพาะสำหรับการทดสอบนี่คือสิ่งที่ดูเหมือนว่า:

from .base import *

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

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

LOGGING = {}

ฉันใส่ตัวแปรสภาพแวดล้อมในการDJANGO_SETTINGS_MODULE=settings.test/etc/environment


0

หากคุณมีโมดูล initaliser ที่แตกต่างกันสำหรับการทดสอบ dev และการผลิตคุณสามารถปิดการใช้งานอะไรก็ได้หรือเปลี่ยนเส้นทางใน initialser ฉันมี local.py, test.py และ production.py ที่สืบทอดมาจาก common.y

common.py ทำการกำหนดค่าหลักทั้งหมดรวมถึงตัวอย่างนี้:

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

จากนั้นใน test.py ฉันมีสิ่งนี้:

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

สิ่งนี้แทนที่ตัวจัดการคอนโซลด้วย FileHandler และหมายความว่ายังคงได้รับการบันทึก แต่ฉันไม่ต้องแตะต้องฐานรหัสการผลิต


0

หากคุณกำลังใช้pytest:

เนื่องจาก pytest รวบรวมข้อความบันทึกและแสดงเฉพาะสำหรับการทดสอบที่ล้มเหลวโดยทั่วไปคุณไม่ต้องการปิดใช้งานการบันทึกใด ๆ ให้ใช้settings.pyไฟล์แยกต่างหากสำหรับการทดสอบ (เช่น, test_settings.py) และเพิ่มลงไป:

LOGGING_CONFIG = None

นี่เป็นการบอก Django เพื่อข้ามการกำหนดค่าการบันทึกทั้งหมด การLOGGINGตั้งค่าจะถูกละเว้นและสามารถลบออกได้จากการตั้งค่า

ด้วยวิธีนี้คุณจะไม่ได้รับการบันทึกสำหรับการทดสอบที่ผ่านและคุณจะได้รับการบันทึกที่มีอยู่ทั้งหมดสำหรับการทดสอบที่ล้มเหลว

pytestการทดสอบจะทำงานโดยใช้การเข้าสู่ระบบที่ถูกจัดตั้งขึ้นโดย สามารถกำหนดค่าตามความชอบของคุณในการpytestตั้งค่า (เช่นtox.ini) ในการรวมข้อความบันทึกระดับการดีบักให้ใช้log_level = DEBUG(หรืออาร์กิวเมนต์บรรทัดคำสั่งที่สอดคล้องกัน)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.