Как подтвердить вывод с помощью тестов на нос / юнит-тестов в Python? - PullRequest
97 голосов
/ 19 ноября 2010

Я пишу тесты для функции, подобной следующей:

def foo():
    print 'hello world!'

Поэтому, когда я хочу проверить эту функцию, код будет выглядеть так:Я запускаю тесты носа с параметром -s, тест вылетает.Как я могу поймать вывод с помощью unittest или переносного модуля?

Ответы [ 9 ]

101 голосов
/ 01 августа 2013

Я использую этот менеджер контекста для захвата вывода.В конечном итоге он использует ту же технику, что и некоторые другие ответы, временно заменив sys.stdout.Я предпочитаю менеджер контекста, потому что он объединяет всю бухгалтерию в одну функцию, поэтому мне не нужно переписывать какой-либо код try-finally, и мне не нужно писать функции setup и teardown только для этого.

import sys
from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

Используйте это так:

with captured_output() as (out, err):
    foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')

Кроме того, поскольку исходное состояние вывода восстанавливается при выходе из блока with, мы можем установить второй блок захвата в той же функциив качестве первого, что невозможно при использовании функций setup и teardown, и становится многословным при написании блоков try-finally вручную.Эта способность пригодилась, когда целью теста было сравнение результатов двух функций относительно друг друга, а не с каким-то предварительно вычисленным значением.

56 голосов
/ 19 ноября 2010

Если вы действительно хотите это сделать, вы можете переназначить sys.stdout на время теста.

def test_foo():
    import sys
    from foomodule import foo
    from StringIO import StringIO

    saved_stdout = sys.stdout
    try:
        out = StringIO()
        sys.stdout = out
        foo()
        output = out.getvalue().strip()
        assert output == 'hello world!'
    finally:
        sys.stdout = saved_stdout

Однако, если бы я писал этот код, я бы предпочел передать необязательный параметр out в функцию foo.

def foo(out=sys.stdout):
    out.write("hello, world!")

Тогда тест намного проще:

def test_foo():
    from foomodule import foo
    from StringIO import StringIO

    out = StringIO()
    foo(out=out)
    output = out.getvalue().strip()
    assert output == 'hello world!'
46 голосов
/ 02 октября 2012

Начиная с версии 2.7, вам больше не нужно переназначать sys.stdout, это обеспечивается с помощью buffer flag . Более того, это стандартное поведение проверки носа.

Вот пример сбоя в небуферизованном контексте:

import sys
import unittest

def foo():
    print 'hello world!'

class Case(unittest.TestCase):
    def test_foo(self):
        foo()
        if not hasattr(sys.stdout, "getvalue"):
            self.fail("need to run in buffered mode")
        output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
        self.assertEquals(output,'hello world!')

Вы можете установить буфер через unit2 флаг командной строки -b, --buffer или в параметрах unittest.main. Противоположность достигается с помощью nosetest flag --nocapture.

if __name__=="__main__":   
    assert not hasattr(sys.stdout, "getvalue")
    unittest.main(module=__name__, buffer=True, exit=False)
    #.
    #----------------------------------------------------------------------
    #Ran 1 test in 0.000s
    #
    #OK
    assert not hasattr(sys.stdout, "getvalue")

    unittest.main(module=__name__, buffer=False)
    #hello world!
    #F
    #======================================================================
    #FAIL: test_foo (__main__.Case)
    #----------------------------------------------------------------------
    #Traceback (most recent call last):
    #  File "test_stdout.py", line 15, in test_foo
    #    self.fail("need to run in buffered mode")
    #AssertionError: need to run in buffered mode
    #
    #----------------------------------------------------------------------
    #Ran 1 test in 0.002s
    #
    #FAILED (failures=1)
26 голосов
/ 08 июля 2015

Многие из этих ответов не дали мне результатов, потому что вы не можете from StringIO import StringIO в Python 3. Вот минимальный рабочий фрагмент, основанный на комментарии @ naxa и Поваренной книге Python.

from io import StringIO
from unittest.mock import patch

with patch('sys.stdout', new=StringIO()) as fakeOutput:
    print('hello world')
    self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')
20 голосов
/ 04 сентября 2016

В Python 3.5 вы можете использовать contextlib.redirect_stdout() и StringIO().Вот модификация вашего кода

import contextlib
from io import StringIO
from foomodule import foo

def test_foo():
    temp_stdout = StringIO()
    with contextlib.redirect_stdout(temp_stdout):
        foo()
    output = temp_stdout.getvalue().strip()
    assert output == 'hello world!'
15 голосов
/ 05 октября 2011

Я только изучаю Python и столкнулся с проблемой, аналогичной приведенной выше, с юнит-тестами для методов с выводом.Мой тестовый модуль для модуля foo, описанный выше, в итоге выглядел так:

import sys
import unittest
from foo import foo
from StringIO import StringIO

class FooTest (unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_foo(self):
        foo()
        self.assertEqual(sys.stdout.getvalue(),'hello world!\n')
10 голосов
/ 19 ноября 2010

Написание тестов часто показывает нам лучший способ написания нашего кода.Подобно ответу Шейна, я хотел бы предложить еще один способ взглянуть на это.Вы действительно хотите утверждать, что ваша программа вывела определенную строку или просто построила определенную строку для вывода?Это становится проще для тестирования, поскольку мы можем предположить, что оператор Python print выполняет свою работу правильно.

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

Тогда ваш тест очень прост:

def test_foo_msg():
    assert 'hello world' == foo_msg()

КонечноЕсли вам действительно нужно проверить фактический вывод вашей программы, не стесняйтесь игнорировать.:)

5 голосов
/ 23 октября 2017

Основываясь на ответе Роба Кеннеди, я написал классную версию диспетчера контекста для буферизации вывода.

Использование как:

with OutputBuffer() as bf:
    print('hello world')
assert bf.out == 'hello world\n'

Вот реализация:

from io import StringIO
import sys


class OutputBuffer(object):

    def __init__(self):
        self.stdout = StringIO()
        self.stderr = StringIO()

    def __enter__(self):
        self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
        sys.stdout, sys.stderr = self.stdout, self.stderr
        return self

    def __exit__(self, exception_type, exception, traceback):
        sys.stdout, sys.stderr = self.original_stdout, self.original_stderr

    @property
    def out(self):
        return self.stdout.getvalue()

    @property
    def err(self):
        return self.stderr.getvalue()
2 голосов
/ 23 июня 2015

Или рассмотрите возможность использования pytest, он имеет встроенную поддержку для утверждения stdout и stderr. См. документы

def test_myoutput(capsys): # or use "capfd" for fd-level
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    print("next")
    captured = capsys.readouterr()
    assert captured.out == "next\n"
...