【问题标题】:python testsuite doesn't show output on consolepython testsuite不在控制台上显示输出
【发布时间】:2016-07-05 06:58:48
【问题描述】:

这是我的示例代码

class StreamToLogger(object):
    def __init__(self, logger, log_level=logging.INFO):
        self.logger = logger
        self.log_level = log_level
        self.linebuf = ''

    def write(self, buf):
        for line in buf.rstrip().splitlines():
            self.logger.log(self.log_level, line.rstrip())



logging.basicConfig(level=logging.DEBUG,
                    format='%(asctime)s:%(levelname)s:%(name)s:%(message)s',
                    filename="logger.log",
                    filemode='w'
                    )

stdout_logger = logging.getLogger('STDOUT')
sl = StreamToLogger(stdout_logger, logging.INFO)
sys.stdout = sl

stderr_logger = logging.getLogger('STDERR')
sl = StreamToLogger(stderr_logger, logging.ERROR)
sys.stderr = sl

suite = suite()
tests = unittest.TextTestRunner(descriptions=True, verbosity=2).run(suite)

作为TextTestRunner 默认使用流作为stderr。我已经使用 stderr 将输出写入日志文件并且它正在工作。我们可以在同一个脚本中多次使用stderr吗?

【问题讨论】:

    标签: python stdout stderr python-unittest


    【解决方案1】:

    我对您的代码进行了新的分析。问题来了

    1. 您为 stdout 和 stderr 定义了两个流。 TextTextRunner 仅使用一个流:默认情况下为 sys.stderr。
    2. TextTestRunner 默认使用 sys.stderr,这不是您的 sys.stderr。这似乎很奇怪,请稍后再看。因此,您的代码就像没有定义任何其他输出流一样工作。

    当您调用 TextTextRunner() 时,情况会发生变化,而不是使用其默认值,而是如果您显式传递您重新定义的 sys.stderr。现在输出将被重定向到您的文件:

    # does not at all what you want (writes output to stderr):
    tests = unittest.TextTestRunner( descriptions=True, verbosity=2 ).run(suite)
    # does not everything what you want (writes output to file only):
    tests = unittest.TextTestRunner\
                               ( stream=sys.stderr, descriptions=True, verbosity=2 ).run(suite) 
    

    满足一个要求(写入文件),但另一个不满足。这是因为只使用了您定义的流之一(您通过流传递给 TextTestRunner 的流)。对于第二个要求(也写入控制台),必须有一种方法可以从一个流(重定向的 stderr)写入两个输出设备。我认为这只有在您以适当的方式更改 StreamToLogger.write() 方法时才有可能:

    def write( self, buf ) :
        my_console_stream_instance.write(buf)
        my_file_stream_instance.write(buf)
    

    其中my_console_stream_instance 是写入控制台的流,my_file_stream_instance 是写入文件的流。所以请看下面的例子,它应该做你想做的事:

    class StreamToLogger( object ):
        def __init__( self ):
            self.terminal = sys.stderr
            self.log = open( "logger.log", "w" )
    
        def write( self, buf ):
            self.terminal.write(buf) # write to stderr
            self.log.write(buf) # write to file
    
    sys.stderr = StreamToLogger()
    
    suite = suite()
    tests = unittest.TextTestRunner\
                             ( stream=sys.stderr, descriptions=True, verbosity=2 ).run( suite )
    

    现在实际上有两个输出:一个到屏幕,一个到文件。

    但是为什么需要将 sys.stderr 传递给 TextTestRunner 的流参数呢?乍一看似乎是一样的。但是在function definitions一章的Python语言参考中是解释:

    默认参数值在函数定义为 执行。这意味着表达式被计算一次,当 定义函数,并使用相同的“预先计算”值 每次通话。这对于了解何时 默认参数是一个可变对象。

    TextTestRunner 中流参数的默认 sys.stderr 在执行 stderr 重定义之前进行评估。这就是为什么我们无论如何都必须通过我们重新定义的 sys.stderr。一个小证明是:你导入 unittest 之前重新定义 sys.stderr:代码在不通过重新定义的 sys.stderr 的情况下运行良好。

    【讨论】:

    • 感谢洪巴兰!对于冗长且易于理解的答案。此代码和逻辑适用于我的案例。
    • 我很高兴能够提供帮助。考虑您的问题对我来说也是一个好处,因为“默认参数行为”的原因对我来说并不是很清楚,现在他们是。
    【解决方案2】:

    对我来说,你想做什么还不是很清楚。将日志记录模块的所有控制台输出写入文件和控制台?如果是这样的话:

    # Create a logging object
    logger = logging.getLogger ( 'STDOUT+FILE' )
    logger.setLevel ( logging.DEBUG )
    
    # Create a file handler and set level to INFO (or other value)
    file_ch = logging.FileHandler( 'logger.log', 'w' )
    file_ch.setLevel ( logging.DEBUG )
    # ... and add it to the logging object
    logger.addHandler( file_ch )
    
    # Create a stream  handler and set level to INFO
    console_ch = logging.StreamHandler( )
    console_ch.setLevel ( logging.DEBUG )
    # ... and add it to the logging object
    logger.addHandler( console_ch )
    
    # Do some logging
    logger.debug( 'Logging Test' )
    logger.debug( '------------' )
    logger.info( 'this is an info log' )
    logger.warn( 'this is a warn log' )
    logger.debug( 'this is a debug log' )
    logger.error( 'this is an error log' )
    logger.critical( 'this is a critical log' )
    

    myLogFile.log 的内容现在是

    Logging Test
    ------------ 
    this is an info log 
    this is a warn log 
    this is a debug log 
    this is a critical log
    

    同样的文字出现在控制台上

    更多信息here

    【讨论】:

    • 我想将从tests = unittest.TextTestRunner(descriptions=True, verbosity=2).run(suite) 获取的输出写入记录器文件和控制台。目前它只写在记录器文件中,而不是在控制台上。
    • Amith Koujalgi 在this post 中的答案是解决方案。只需在 TextTestRunner() 调用中添加 stream=sys.stdout 和提到的类 Logger
    • 该解决方案不适合我。我认为我的示例代码中的 testrunnner 的 stderrsys.stderr = sl 之间存在一些冲突(参考问题)
    • 您是否按照我 2 小时前的上一条评论中提到的 stdout 尝试过?