2
votes

comment vérifier le format du journal python en test unitaire?

Récemment, j'écris une extension de journalisation python et je souhaite ajouter des tests pour mon extension afin de vérifier si mon extension fonctionne comme prévu.
Cependant, je ne sais pas comment capturer le journal complet et le comparer avec mon résultat excepté dans unittest / pytest.

exemple simplifié :

err = ''

Voici mes tests

Tentative 1: unittest

err = 'app-DEBUG-hello'
  • comportement attendu:
    def test_logger(capsys):
        app_logger.debug('hello')
        out, err = capsys.readouterr()
        assert err
        assert err == 'app-DEBUG-hello'
    
  • comportement réel
    caplog.text = 'test_logger.py               6 DEBUG    hello'
    

Tentative 2: pytest caplog

caplog.text = 'app-DEBUG-hello'
  • comportement attendu:
    from app import app_logger
    import pytest
    
    
    def test_logger(caplog):
        app_logger.debug('hello')
        assert caplog.text == 'app-DEBUG-hello'
    
  • comportement réel
    cm.output = ['DEBUG:app:hello']
    

Tentative 3: pytest capsys

depuis l'application import app_logger import pytest

cm.output = 'app-DEBUG-hello'
  • comportement attendu:
    from app import app_logger
    import unittest
    
    
    class TestApp(unittest.TestCase):
    
        def test_logger(self):
            with self.assertLogs('', 'DEBUG') as cm:
                app_logger.debug('hello')
            # or some other way to capture the log output.
            self.assertEqual('app-DEBUG-hello', cm.output)
    
  • comportement réel
    # app.py
    import logging
    def create_logger():
        formatter = logging.Formatter(fmt='%(name)s-%(levelname)s-%(message)s')
        hdlr = logging.StreamHandler()
        hdlr.setFormatter(formatter)
        logger = logging.getLogger(__name__)
        logger.setLevel('DEBUG')
        logger.addHandler(hdlr)
        return logger
    
    
    app_logger = create_logger()
    

Étant donné qu'il y aura de nombreux tests avec différents formats, je ne veux pas vérifier le format du journal manuellement. Je n'ai aucune idée de comment obtenir le journal complet comme je le vois sur la console et le comparer avec mon attendu dans les cas de test. En espérant votre aide, merci.


3 commentaires

C'est parce que votre gestionnaire sera ignoré lorsque les enregistrements de journal émis sont capturés, à la fois par unittest et pytest . En ce qui concerne l'application du formatage personnalisé, le support est soit complètement absent ( unittest ne prend pas du tout en charge les formateurs personnalisés) soit assez limité (avec pytest , vous pouvez au moins vous passer chaîne de format personnalisé via la ligne de commande: pytest --log-format = "% (name) s -% (levelname) s -% (message) s" , mais les classes de formateur personnalisées seront également ignorées ).


Cependant, tester le format des enregistrements collectés tout au long du programme rapporte rarement de toute façon; divisez vos tests en: ceux qui vérifient si les enregistrements du journal sont correctement émis (par exemple, vous ne manquez pas un enregistrement lors de l'exécution d'une fonction) et ceux qui valident vos gestionnaires et formateurs personnalisés (créez un enregistrement, appelez le gestionnaire . émettre (record) / formatter.format (record) explicilty et vérifier s'ils ont bien fait leur travail).


@hoefling compris. Merci pour votre réponse rapide :) Ce que mon extension fait principalement est d'ajouter un champ supplémentaire pour le connecter et de le formater avec le format spécifié. Il semble qu'il soit un peu difficile de vérifier son format, mais vous me fournissez un autre moyen de le réaliser. Je vais essayer. Je vous remercie.


3 Réponses :


1
votes

Après avoir lu le code source de la bibliothèque unittest , j'ai élaboré le contournement suivant. Notez qu'il fonctionne en modifiant un membre protégé d'un module importé, il peut donc être interrompu dans les versions futures.

from unittest.case import _AssertLogsContext
_AssertLogsContext.LOGGING_FORMAT = 'same format as your logger'

Après ces commandes, le contexte de journalisation est ouvert par self.assertLogs code > utilisera le format ci-dessus. Je ne sais vraiment pas pourquoi cette valeur est restée codée en dur et non configurable.

Je n'ai pas trouvé d'option pour lire le format d'un enregistreur, mais si vous utilisez logging.config. dictConfig vous pouvez utiliser une valeur du même dictionnaire.


0 commentaires

1
votes

Je sais que c'est vieux, mais poster ici car il s'est retrouvé dans Google pour moi ...

Il faudrait probablement un nettoyage, mais c'est la première chose qui s'est rapprochée pour moi, alors j'ai pensé que ce serait bien à partager.

Voici un mixin de cas de test que j'ai mis en place qui me permet de vérifier qu'un gestionnaire particulier est formaté comme prévu en copiant le formateur:

class SimpleLogTests(SetupLoggingMixin, SimpleTestCase):
    def test_logged_time(self):
        msg = 'foo'
        self.root_logger.error(msg)
        self.assertEqual(self.stream.getvalue(), 'my-expected-message-formatted-as-expected')

Et voici un exemple de son utilisation:

import io
import logging

from django.conf import settings
from django.test import SimpleTestCase
from django.utils.log import DEFAULT_LOGGING

class SetupLoggingMixin:
    def setUp(self):
        super().setUp()
        logging.config.dictConfig(settings.LOGGING)
        self.stream = io.StringIO()
        self.root_logger = logging.getLogger("")
        self.root_hdlr = logging.StreamHandler(self.stream)

        console_handler = None
        for handler in self.root_logger.handlers:
            if handler.name == 'console':
                console_handler = handler
                break

        if console_handler is None:
            raise RuntimeError('could not find console handler')

        formatter = console_handler.formatter
        self.root_formatter = formatter
        self.root_hdlr.setFormatter(self.root_formatter)
        self.root_logger.addHandler(self.root_hdlr)

    def tearDown(self):
        super().tearDown()
        self.stream.close()
        logging.config.dictConfig(DEFAULT_LOGGING)


0 commentaires

0
votes

Je sais que cela ne répond pas complètement à la question du PO, mais je suis tombé sur ce message en cherchant un moyen efficace de capturer les messages enregistrés.

En prenant ce que @ user319862 a fait, je l'ai nettoyé et simplifié.

import unittest
import logging

from io import StringIO

class SetupLogging(unittest.TestCase):
    def setUp(self):
        super().setUp()
        self.stream = StringIO()
        self.root_logger = logging.getLogger("")
        self.root_hdlr = logging.StreamHandler(self.stream)
        self.root_logger.addHandler(self.root_hdlr)

    def tearDown(self):
        super().tearDown()
        self.stream.close()

    def test_log_output(self):
        """ Does the logger produce the correct output? """
        msg = 'foo'
        self.root_logger.error(msg)
        self.assertEqual(self.stream.getvalue(), 'foo\n')


if __name__ == '__main__':
    unittest.main()


0 commentaires