Python'da nosetest / unittest ile çıktı nasıl doğrulanır?


114

Bir sonraki gibi bir işlev için testler yazıyorum:

def foo():
    print 'hello world!'

Bu işlevi test etmek istediğimde kod şu şekilde olacak:

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

Ama -s parametresiyle nosetest çalıştırırsam, test çöküyor. Çıkışı Unittest veya Burun Modülü ile nasıl yakalayabilirim?


Yanıtlar:


125

Çıktıyı yakalamak için bu bağlam yöneticisini kullanıyorum . Nihayetinde, geçici olarak değiştirerek diğer yanıtların bazıları ile aynı tekniği kullanır sys.stdout. Bağlam yöneticisini tercih ediyorum çünkü tüm defter tutmayı tek bir işleve sarıyor, bu yüzden herhangi bir deneme kodunu yeniden yazmak zorunda değilim ve sadece bunun için kurulum ve sökme işlevlerini yazmak zorunda değilim.

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

Bunu şu şekilde kullanın:

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

Ayrıca, orijinal çıktı durumu withbloktan çıkıldığında geri yüklendiğinden, ilkiyle aynı işlevde ikinci bir yakalama bloğu oluşturabiliriz, bu kurulum ve sökme işlevlerini kullanarak mümkün değildir ve dene-nihayet yazarken kelimelere dökülür. manuel olarak engeller. Bu yetenek, bir testin amacı önceden hesaplanmış bir değerden ziyade iki işlevin sonuçlarını birbirine göre karşılaştırmak olduğunda kullanışlı oldu.


Bu pep8radius'ta benim için gerçekten iyi çalıştı . Ancak son zamanlarda, bunu tekrar kullandım ve yazdırırken aşağıdaki hatayı aldım TypeError: unicode argument expected, got 'str'(yazdırmaya aktarılan tür (str / unicode) alakasız).
Andy Hayden

9
Hmmm, python 2'de istediğimiz from io import BytesIO as StringIOve python 3'te sadece istediğimiz olabilir from io import StringIO. Sanırım testlerimde sorunu çözmüş gibiydi.
Andy Hayden

4
Ooop, bitirmek için, birçok mesaj için özür dileriz. Bunu bulan insanlara açıklık getirmek için: python3 io.StringIO, python 2 ise StringIO.StringIO kullanın! Tekrar teşekkürler!
Andy Hayden

Neden tüm örnekler burada aradığınız strip()üzerine unicodedöndü StringIO.getvalue()?
Palimondo

1
Hayır, @Vedran. Bu, ait olan ismin yeniden bağlanmasına dayanır sys. Import deyiminizle, değerinin bir kopyasını alan adlı yerel bir değişken oluşturuyorsunuz . Birinde yapılan değişiklikler diğerine yansıtılmaz. stderrsys.stderr
Rob Kennedy

60

Bunu gerçekten yapmak istiyorsanız, sys.stdout'u test süresi boyunca yeniden atayabilirsiniz.

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

Ancak bu kodu yazıyor olsaydım out, fooişleve isteğe bağlı bir parametre geçirmeyi tercih ederim .

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

O zaman test çok daha basit:

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
Not: python 3.x altında, StringIOsınıf artık iomodülden içe aktarılmalıdır . from io import StringIOpython 2.6+ ile çalışır.
Bryan P

2
Eğer kullanırsanız from io import StringIOpiton 2'de, bir olsun TypeError: unicode argument expected, got 'str'yazdırırken.
matiasg

9
Hızlı not: python 3.4'te, bunu istisnai güvenli bir şekilde yapmak için contextlib.redirect_stdout bağlam yöneticisini kullanabilirsiniz:with redirect_stdout(out):
Lucretiel

2
Bunu yapmanıza gerek yok saved_stdout = sys.stdout, her zaman bunun için sihirli bir referansınız var sys.__stdout__, örneğin, yalnızca sys.stdout = sys.__stdout__temizlemenize ihtiyacınız var .
ThorSummoner

@ThorSummoner Teşekkürler, bu sadece bazı testlerimi basitleştirdi ... başrol oynadığını gördüğüm scuba için .... küçük dünya!
Jonathon Reinhart

48

2.7 sürümünden bu yana, artık yeniden atamanıza gerek yoktur sys.stdout, bu, bufferbayrak aracılığıyla sağlanır . Dahası, nosetest'in varsayılan davranışıdır.

Arabelleğe alınmamış bağlamda başarısız olan bir örnek:

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

Sen aracılığıyla tampon ayarlayabilirsiniz unit2komut satırı bayrağıyla -b, --bufferya da unittest.mainseçenekleri. Bunun tersi nosetestbayrakla sağlanır --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)

Bunun aşağıdakilerle etkileşime girdiğini unutmayın --nocapture; özellikle, bu bayrak ayarlanırsa, arabelleğe alınmış mod devre dışı bırakılacaktır. Dolayısıyla, çıktıyı uçbirimde görme veya çıktının beklendiği gibi olup olmadığını test etme seçeneğiniz vardır.
ijoseph

1
İpdb.set_trace () gibi bir şey kullanırken hata ayıklamayı çok zorlaştırdığından, bunu her test için açıp kapatmak mümkün mü?
Lqueryvg

33

from StringIO import StringIOPython 3'te yapamayacağınız için bu yanıtların çoğu benim için başarısız oldu . İşte @ naxa'nın yorumuna ve Python Cookbook'a dayanan minimum çalışma parçacığı.

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
Bunu Python 3 için seviyorum, temiz!
Sylhare

1
Bu sayfadaki benim için çalışan tek çözüm buydu! Teşekkür ederim.
Justin Eyster

24

Python 3.5'te contextlib.redirect_stdout()ve kullanabilirsiniz StringIO(). İşte kodunuzda yapılan değişiklik

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

Mükemmel cevap! Belgelere göre bu, Python 3.4'e eklenmiştir.
Hypercube

Redirect_stdout için 3.4 ve redirect_stderr için 3.5. belki de karışıklığın ortaya çıktığı yer burasıdır!
rbennell

redirect_stdout()ve redirect_stderr()girdi argümanını döndürür. Yani, with contextlib.redirect_stdout(StringIO()) as temp_stdout:hepinizi tek bir satırda verir. 3.7.1 ile test edilmiştir.
Adrian W

17

Sadece Python öğreniyorum ve kendimi çıktılı yöntemler için birim testleri ile yukarıdakine benzer bir problemle boğuşurken buldum. Yukarıdaki foo modülü için geçen birim testim şöyle göründü:

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()ile karşılaştırarak hile yapıp hile yapmamak isteyebilirsiniz\n
Silviu

StringIO modülü kullanımdan kaldırıldı. Bunun yerinefrom io import StringIO
Edwarric

10

Yazma testleri genellikle bize kodumuzu yazmanın daha iyi bir yolunu gösterir. Shane'in cevabına benzer şekilde, buna başka bir bakış açısı daha önermek istiyorum. Programınızın belirli bir dizge çıktısını verdiğini veya çıktı için belirli bir dizge oluşturduğunu gerçekten iddia etmek istiyor musunuz ? Python printifadesinin işini doğru yaptığını varsayabileceğimiz için bunu test etmek daha kolay hale gelir .

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

O zaman testin çok basit:

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

Elbette, programınızın gerçek çıktısını gerçekten test etmeniz gerekiyorsa, göz ardı etmekten çekinmeyin. :)


1
ama bu durumda foo test edilmeyecek ... belki bu bir sorun
Pedro Valencia

5
Test eden bir pürist bakış açısından, belki de bir problemdir. Pratik bir bakış açısıyla, foo()herhangi bir şey yapmazsanız, ancak print deyimini çağırırsanız, muhtemelen bir problem değildir .
Alison R.

5

Rob Kennedy'nin cevabına dayanarak, çıktıyı tamponlamak için bağlam yöneticisinin sınıf tabanlı bir sürümünü yazdım.

Kullanım şuna benzer:

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

İşte uygulama:

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

Veya kullanmayı düşünün pytest, stdout ve stderr'yi onaylamak için yerleşik desteğe sahiptir. Belgelere bakın

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"

İyi bir. Bağlantılar kaybolabileceğinden ve içerik değişebileceğinden minimal bir örnek ekleyebilir misiniz?
KobeJohn

2

Hem n611x007 ve Numen zaten kullanmakta önerdi unittest.mock, ama bu bir çözüm adapte Acumenus en kolayca sarın yaşayabileceğimizi göstermek için unittest.TestCasebir alay ile etkileşim yöntemlerini 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

Bu konudaki tüm harika cevaplara dayanarak, onu böyle çözdüm. Mümkün olduğu kadar stokta tutmak istedim. Ben kullanarak birim test mekanizmasını artar setUp()yakalanmasını sys.stdoutve sys.stderryeni assert API'ler beklenen değere karşı yakalanan değerleri kontrol edin ve sonra geri eklendi sys.stdoutve sys.stderrüzerine tearDown(). 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()

Birim testi çalıştırıldığında, çıktı:

$ 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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.