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


114

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

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モジュールで出力をキャッチするにはどうすればよいですか?


回答:


124

このコンテキストマネージャを使用して、出力をキャプチャします。一時的に置き換えることにより、最終的には他のいくつかの回答と同じ手法を使用しsys.stdoutます。すべての簿記を単一の関数にラップするので、コンテキストマネージャーを好みます。これにより、try-finallyコードを書き直す必要がなく、このためだけにセットアップ関数とティアダウン関数を記述する必要がありません。

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番目のキャプチャブロックを設定できます。これは、セットアップ関数とティアダウン関数を使用して行うことはできません。手動でブロックします。テストの目的が、2つの関数の結果を、事前に計算された値ではなく、相互に比較することである場合に、この機能が役立ちます。


これは私にとってpep8radiusで本当にうまくいきました。しかし最近、私はこれを再度使用しており、印刷時に次のエラーが発生しましたTypeError: unicode argument expected, got 'str'(print(str / unicode)に渡されるタイプは無関係です)。
Andy Hayden

9
うーん、Python 2では必要なのかもしれませんがfrom io import BytesIO as StringIO、Python 3ではちょうどそうなのかもしれませんfrom io import StringIO。私が思う私のテストで問題を修正するように思われました。
Andy Hayden

4
おっと、終わりになりましたが、メッセージが多すぎてお詫びします。これを見つけた人々のために明確にするために:python3はio.StringIOを使用し、python 2はStringIO.StringIOを使用します!再度、感謝します!
Andy Hayden

なぜここにあるすべての例は strip()unicodeはからの戻りをしているのStringIO.getvalue()ですか?
Palimondo 2017年

1
いいえ、@ Vedran。これは、に属する名前の再バインドに依存していsysます。importステートメントを使用して、の値のコピーを受け取ったという名前のローカル変数を作成しています。一方に対する変更は、もう一方には反映されません。stderrsys.stderr
ロブ・ケネディ

60

本当にこれを実行したい場合は、テストの間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!'

11
注:python 3.xでは、StringIOクラスをioモジュールからインポートする必要があります。from io import StringIOPython 2.6以降で動作します。
ブライアンP

2
from io import StringIOPython 2で使用するとTypeError: unicode argument expected, got 'str'、印刷時にaを取得します。
matiasg 2014年

9
クイック注:のpython 3.4で、使用することができますcontextlib.redirect_stdoutの例外安全な方法でこれを行うには、コンテキストマネージャを:with redirect_stdout(out):
Lucretiel

2
あなたがする必要はありませんsaved_stdout = sys.stdout、あなたはいつもこれに魔法の参照を持っていsys.__stdout__ます、例えばあなたsys.stdout = sys.__stdout__はあなたのクリーンアップでのみ必要です。
ThorSummoner、2015年

@ThorSummonerおかげで、これは私のテストの一部を簡略化しただけです... あなたが主演したスキューバのために...小さな世界!
Jonathon Reinhart、2016年

48

バージョン2.7以降、を再割り当てする必要はありませんsys.stdout。これは、bufferフラグます。。さらに、これは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オプション。反対は、nosetestflag によって実現されます--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)

これはと相互作用することに注意してください--nocapture。特に、このフラグが設定されている場合、バッファモードは無効になります。そのため、端末で出力を確認するか、出力が期待どおりかどうかをテストするを選択できます。
ijoseph 2017

1
ipdb.set_trace()などを使用するとデバッグが非常に困難になるため、テストごとにこれをオンまたはオフにすることは可能ですか?
Lqueryvg

33

from StringIO import StringIOPython 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')

3
私はこれをPython 3で気に入っています。
Sylhare、2018

1
これは、このページで私のために働いた唯一の解決策でした!ありがとうございました。
Justin Eyster

24

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

正解です。ドキュメントによると、これはPython 3.4で追加されました。
ハイパーキューブ

redirect_stdoutは3.4、redirect_stderrは3.5です。多分それは混乱が生じたところです!
rbennell

redirect_stdout()redirect_stderr()入力引数を返します。つまり、with contextlib.redirect_stdout(StringIO()) as temp_stdout:すべてを1行で表示します。3.7.1でテスト済み。
エイドリアンW

17

私は単に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')

5
あなたは何をしたいかもしれsys.stdout.getvalue().strip()ないとの比較カンニング\n:)
Silviu

StringIOモジュールは非推奨です。代わりにfrom io import StringIO
Edwarric

10

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

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

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

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

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


1
しかし、この場合、fooはテストされません...多分それは問題です
Pedro Valencia

5
テスト純粋主義者の観点から、おそらくそれは問題です。実用的な観点から見ると、foo()何もせずにprintステートメントを呼び出すだけの場合は、おそらく問題ではありません
アリソンR.

5

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

2

または、の使用を検討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"

いいね。リンクが消えて内容が変わる可能性があるため、最小限の例を含めることができますか?
KobeJohn

2

どちらもn611x007物自体は、すでに使用して提案しunittest.mockたが、この答えは適応アキュメナスのあなたが簡単にラップすることができる方法を示すためにunittest.TestCase嘲笑と対話する方法をstdout

import io
import unittest
import unittest.mock

msg = "Hello World!"


# function we will be testing
def foo():
    print(msg, end="")


# create a decorator which wraps a TestCase method and pass it a mocked
# stdout object
mock_stdout = unittest.mock.patch('sys.stdout', new_callable=io.StringIO)


class MyTests(unittest.TestCase):

    @mock_stdout
    def test_foo(self, stdout):
        # run the function whose output we want to test
        foo()
        # get its output from the mocked stdout
        actual = stdout.getvalue()
        expected = msg
        self.assertEqual(actual, expected)

0

このスレッドのすべての素晴らしい答えに基づいて、これが私が解決した方法です。在庫はできるだけ残したかった。私は使用してユニットテスト機構増強setUp()捕捉にするsys.stdoutsys.stderr、新しいアサートAPIは期待値に対して捕捉値を確認した後、復元するために添加sys.stdoutし、sys.stderrtearDown(). I did this to keep a similar unit test API as the built-inunittestのAPI while still being able to unit test values printed tosys.stdoutのorsys.stderr`。

import io
import sys
import unittest


class TestStdout(unittest.TestCase):

    # before each test, capture the sys.stdout and sys.stderr
    def setUp(self):
        self.test_out = io.StringIO()
        self.test_err = io.StringIO()
        self.original_output = sys.stdout
        self.original_err = sys.stderr
        sys.stdout = self.test_out
        sys.stderr = self.test_err

    # restore sys.stdout and sys.stderr after each test
    def tearDown(self):
        sys.stdout = self.original_output
        sys.stderr = self.original_err

    # assert that sys.stdout would be equal to expected value
    def assertStdoutEquals(self, value):
        self.assertEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stdout would not be equal to expected value
    def assertStdoutNotEquals(self, value):
        self.assertNotEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stderr would be equal to expected value
    def assertStderrEquals(self, value):
        self.assertEqual(self.test_err.getvalue().strip(), value)

    # assert that sys.stderr would not be equal to expected value
    def assertStderrNotEquals(self, value):
        self.assertNotEqual(self.test_err.getvalue().strip(), value)

    # example of unit test that can capture the printed output
    def test_print_good(self):
        print("------")

        # use assertStdoutEquals(value) to test if your
        # printed value matches your expected `value`
        self.assertStdoutEquals("------")

    # fails the test, expected different from actual!
    def test_print_bad(self):
        print("@=@=")
        self.assertStdoutEquals("@-@-")


if __name__ == '__main__':
    unittest.main()

単体テストを実行すると、出力は次のようになります。

$ python3 -m unittest -v tests/print_test.py
test_print_bad (tests.print_test.TestStdout) ... FAIL
test_print_good (tests.print_test.TestStdout) ... ok

======================================================================
FAIL: test_print_bad (tests.print_test.TestStdout)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tests/print_test.py", line 51, in test_print_bad
    self.assertStdoutEquals("@-@-")
  File "/tests/print_test.py", line 24, in assertStdoutEquals
    self.assertEqual(self.test_out.getvalue().strip(), value)
AssertionError: '@=@=' != '@-@-'
- @=@=
+ @-@-


----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.