web-dev-qa-db-ja.com

Pythonでnosetest / unittestで出力をアサートする方法は?

私は次のような関数のテストを書いています:

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

しかし、-sパラメーターを指定してnosetestsを実行すると、テストがクラッシュします。 unittestまたはnoseモジュールで出力をキャッチするにはどうすればよいですか?

98
Pedro Valencia

これを使用して context manager 出力をキャプチャします。一時的にsys.stdoutを置き換えることにより、最終的に他の回答のいくつかと同じ手法を使用します。コンテキストマネージャーは、すべての簿記を1つの関数にラップするため、コンテキストマネージャーを好みます。

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ブロックを終了すると元の出力状態が復元されるため、最初のブロックと同じ関数で2番目のキャプチャブロックを設定できます。これは、セットアップ関数およびティアダウン関数を使用して不可能です。 try-finallyブロックを手動で書き込む場合は冗長です。この機能は、テストの目的が、事前に計算された値ではなく、相互に関連する2つの関数の結果を比較することである場合に役立ちました。

103
Rob Kennedy

これを本当に行いたい場合は、テスト中に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!'
56
Shane Hathaway

バージョン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)
47
FabienAndre

これらの答えの多くは、あなたがfrom StringIO import StringIO in Python 3.これは、@ naxaのコメントとPython Cookbook。

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')
27
Noumenon

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!'
20
Mudit Jain

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')
15
sean_robbins

テストを書くと、コードを書くためのより良い方法が示されます。シェーンの答えと同様に、これを見るもう一つの方法を提案したいと思います。あなたのプログラムが特定の文字列を出力したことを本当に主張したいのですか、それとも特定の文字列を出力用に構築したのですか?これは、おそらくPython printステートメントが正しく機能すると仮定できるため、テストが容易になります。

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

次に、テストは非常に簡単です。

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

もちろん、プログラムの実際の出力をテストする必要がある場合は、お気軽に無視してください。 :)

10
Alison R.

Rob Kennedyの回答に基づいて、出力をバッファリングするためにコンテキストマネージャのクラスベースバージョンを作成しました。

使用方法は次のとおりです。

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()
5
Hugo Mota

または、pytestの使用を検討してください。stdoutとstderrをアサートするためのサポートが組み込まれています。 docs を参照してください

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"
2
Michel Samia