Подтвердить что ты не робот

Как утверждать вывод с помощью nosetest/unittest в python?

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

def foo():
    print 'hello world!'

Поэтому, когда я хочу протестировать эту функцию, код будет выглядеть следующим образом:

import sys
from foomodule import foo
def test_foo():
    foo()
    output = sys.stdout.getline().strip() # because stdout is an StringIO instance
    assert output == 'hello world!'

Но если я запустил nosetests с параметром -s, тест завершится с ошибкой. Как я могу поймать вывод с помощью unittest или носового модуля?

4b9b3361

Ответ 1

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

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, мы можем настроить второй блок захвата в той же функции, что и первый, что невозможно с помощью функций настройки и разрыва, и получает при написании блоков try-finally вручную. Эта способность полезна, когда целью теста было сравнение результатов двух функций относительно друг друга, а не с каким-то заранее вычисленным значением.

Ответ 2

Если вы действительно хотите это сделать, вы можете переназначить 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!'

Ответ 3

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

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

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

Ответ 4

Я только изучаю Python и обнаружил, что борюсь с аналогичной проблемой с предыдущей версией с модульными тестами для методов с выходом. Мой пропущенный unit test для модуля 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')

Ответ 5

Многие из этих ответов не помогли мне, потому что вы не можете from StringIO import StringIO в Python 3. Здесь минимальный рабочий фрагмент, основанный на комментарии @naxa и Cookbook 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')

Ответ 6

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

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

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

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

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

Ответ 7

В 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!'

Ответ 8

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

Ответ 9

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

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

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