2011-09-19 22 views
36

我正在使用PyDev進行Python應用程序的開發和單元測試。 至於單元測試,一切工作都代表了內容記錄到任何日誌記錄的事實。記錄器不被PyDev的「捕獲輸出」捕獲。PyDev單元測試:如何捕獲記錄到「捕獲的輸出」中的logging.Logger的文本

我已經着一切記錄到這樣的標準輸出:

import sys 
logger = logging.getLogger() 
logger.level = logging.DEBUG 
logger.addHandler(logging.StreamHandler(sys.stdout)) 

儘管如此,「捕獲輸出」不顯示的東西記錄到記錄器。

這裏的示例單元測試腳本:test.py

import sys 
import unittest 
import logging 

logger = logging.getLogger() 
logger.level = logging.DEBUG 
logger.addHandler(logging.StreamHandler(sys.stdout)) 

class TestCase(unittest.TestCase): 
    def testSimpleMsg(self): 
     print("AA") 
     logging.getLogger().info("BB") 

控制檯輸出是:

Finding files... done. 
Importing test modules ... done. 

testSimpleMsg (itf.lowlevel.tests.hl7.TestCase) ... AA 
2011-09-19 16:48:00,755 - root - INFO - BB 
BB 
ok 

---------------------------------------------------------------------- 
Ran 1 test in 0.001s 

OK 

但是CAPTURED OUTPUT用於測試的是:

======================== CAPTURED OUTPUT ========================= 
AA 

有沒有人知道如何在執行這個測試期間捕獲所有記錄到logging.Logger

回答

32

的問題是,unittest亞軍取代sys.stdout/sys.stderr測試開始之前,以及StreamHandler還在寫原來的sys.stdout

如果您將'當前'sys.stdout分配給處理程序,它應該可以工作(請參閱下面的代碼)。

import sys 
import unittest 
import logging 

logger = logging.getLogger() 
logger.level = logging.DEBUG 
stream_handler = logging.StreamHandler(sys.stdout) 
logger.addHandler(stream_handler) 

class TestCase(unittest.TestCase): 
    def testSimpleMsg(self): 
     stream_handler.stream = sys.stdout 
     print("AA") 
     logging.getLogger().info("BB") 

雖然,一個更好的辦法是添加/測試期間移除處理程序:

import sys 
import unittest 
import logging 

logger = logging.getLogger() 
logger.level = logging.DEBUG 

class TestCase(unittest.TestCase): 
    def testSimpleMsg(self): 
     stream_handler = logging.StreamHandler(sys.stdout) 
     logger.addHandler(stream_handler) 
     try: 
      print("AA") 
      logging.getLogger().info("BB") 
     finally: 
      logger.removeHandler(stream_handler) 
+8

爲了完整:我需要爲所有單元測試進行重定向。對我來說最好的解決方案是在setUp-method中添加新的處理程序,並在tearDown方法中刪除它。 – gecco

+1

很好的回答,我[擴展](http://stackoverflow.com/a/15969985/321973)這是一個'__metaclass__',這樣一個包裝的'setUp'和'tearDown'自動包含這個 –

+1

爲什麼添加/刪除處理程序而不是將其保留在測試用例之外? – mlt

14

我從小厭倦了手動添加Fabio's great code所有setUp S的,所以我子類unittest.TestCase與一些__metaclass__ ing:

class LoggedTestCase(unittest.TestCase): 
    __metaclass__ = LogThisTestCase 
    logger = logging.getLogger("unittestLogger") 
    logger.setLevel(logging.DEBUG) # or whatever you prefer 

class LogThisTestCase(type): 
    def __new__(cls, name, bases, dct): 
     # if the TestCase already provides setUp, wrap it 
     if 'setUp' in dct: 
      setUp = dct['setUp'] 
     else: 
      setUp = lambda self: None 
      print "creating setUp..." 

     def wrappedSetUp(self): 
      # for hdlr in self.logger.handlers: 
      # self.logger.removeHandler(hdlr) 
      self.hdlr = logging.StreamHandler(sys.stdout) 
      self.logger.addHandler(self.hdlr) 
      setUp(self) 
     dct['setUp'] = wrappedSetUp 

     # same for tearDown 
     if 'tearDown' in dct: 
      tearDown = dct['tearDown'] 
     else: 
      tearDown = lambda self: None 

     def wrappedTearDown(self): 
      tearDown(self) 
      self.logger.removeHandler(self.hdlr) 
     dct['tearDown'] = wrappedTearDown 

     # return the class instance with the replaced setUp/tearDown 
     return type.__new__(cls, name, bases, dct) 

Now您的測試用例可以簡單地繼承LoggedTestCase,即class TestCase(LoggedTestCase)而不是class TestCase(unittest.TestCase),您就完成了。或者,您可以添加__metaclass__行,並在測試中定義logger或者稍微修改LogThisTestCase

+1

'__metaclass__'的真棒用法 – Randy

+1

@Randy謝謝,閱讀[__metaclass__'這個偉大的解釋](http://stackoverflow.com/a/6581949/321973)我只是_had_使用它... –

+0

我發現我自己上週也讀了相同的答案,並且已經將其寫入我的代碼庫中。 – Randy

5

我建議使用一個LogCapture和測試,你真的是記錄你所期望的要記錄:

http://testfixtures.readthedocs.org/en/latest/logging.html

+0

@Wooble - 對不起,將文檔移動到RTD,所以它們現在位於:http://testfixtures.readthedocs.org/en/latest/ –

+0

我喜歡有一個包裝來照顧它,所以不需要編寫代碼顯然是一個共同的問題。但不幸的是,當我使用「print(l)」時,我常常使用記錄器時遇到了UTF問題。所以我的方法是「sys.stdout.buffer.write(l .__ str __()。encode('utf8'))」。這很好,所以我想分享它。 –

+0

@IwanLD - 我推薦使用'LogCapture().check()'現在。你爲什麼不能使用它? –

1

我碰到這個問題也來了。我結束了StreamHandler的子類化,並使用一個獲取sys.stdout的屬性覆蓋了stream屬性。這樣,處理程序將使用unittest.TestCase已交換到sys的流。標準輸出:

class CapturableHandler(logging.StreamHandler): 

    @property 
    def stream(self): 
     return sys.stdout 

    @stream.setter 
    def stream(self, value): 
     pass 

然後,您可以設置運行測試,像這樣前的日誌處理程序(這將定製的處理程序添加到根logger):

def setup_capturable_logging(): 
    if not logging.getLogger().handlers: 
     logging.getLogger().addHandler(CapturableHandler()) 

如果像我一樣,你有你的測試在單獨的模塊,你可以把每個單元測試模塊的進口,這將確保日誌記錄設置後線測試運行之前:

import logutil 

logutil.setup_capturable_logging() 

這可能不是最乾淨的APPR噢,但它非常簡單,對我來說效果很好。