![](/img/trans.png)
[英]How to test and log multiple times -nosetest unittest - python
[英]How to assert output with nosetest/unittest in 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!'
但是,如果我使用 -s 参数运行 nosetests,测试就会崩溃。 如何使用 unittest 或 nose 模块捕获输出?
我使用这个上下文管理器来捕获输出。 它最终通过临时替换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
块时恢复,我们可以在与第一个捕获块相同的函数中设置第二个捕获块,这使用设置和拆卸函数是不可能的,并且在编写 try- 时变得冗长最后手动阻止。 当测试的目标是将两个函数的结果相互比较而不是与某个预先计算的值进行比较时,这种能力就派上用场了。
如果你真的想这样做,你可以在测试期间重新分配 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!'
从 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)
很多这些答案对我来说都失败了,因为你不能在 Python 3 中from StringIO import StringIO
。这是基于@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')
在 python 3.5 中,您可以使用 contextlib.redirect_stdout( 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,发现自己正在努力解决与上述问题类似的问题,即对具有输出的方法进行单元测试。 我通过上面的 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')
编写测试通常会向我们展示一种更好的编写代码的方法。 与 Shane 的回答类似,我想建议另一种看待这个问题的方法。 您真的要断言您的程序输出了某个字符串,还是只是构造了某个字符串用于输出? 这变得更容易测试,因为我们可以假设 Python print
语句正确地完成了它的工作。
def foo_msg():
return 'hello world'
def foo():
print foo_msg()
那么你的测试就很简单了:
def test_foo_msg():
assert 'hello world' == foo_msg()
当然,如果你真的需要测试你的程序的实际输出,那么可以无视。 :)
n611x007和Noumenon都已经建议使用unittest.mock
,但是这个答案改编了 Acumenus以展示如何轻松包装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)
根据 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()
或者考虑使用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"
Unittest 现在附带上下文管理器(Python 3.7,但也可能是更早的版本)。 你可以这样做:
# example.py
import logging
def method_with_logging():
logging.info("Hello, World!")
然后在你的单元测试中:
# test.py
from unittest import TestCase
from example import method_with_logging
class TestExample(TestCase):
def test_logging(self):
with self.assertLogs() as captured:
method_with_logging()
self.assertEqual(len(captured.records), 1) # check that there is only one log message
self.assertEqual(captured.records[0].getMessage(), "Hello, World!") # and it is the proper one
取自https://pythonin1minute.com/how-to-test-logging-in-python/
基于这个线程中所有很棒的答案,这就是我解决它的方法。 我想尽可能保留它。 我使用setUp()
增强了单元测试机制以捕获sys.stdout
和sys.stderr
,添加了新的断言 API 以根据预期值检查捕获的值,然后在tearDown(). I did this to keep a similar unit test API as the built-in
时恢复sys.stdout
和sys.stderr
。 tearDown(). I did this to keep a similar unit test API as the built-in
API while still being able to unit test values printed to
sys.stdout or
sys.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)
我喜欢sorens 对问题和示例代码的直截了当的 [Answer][1],特别是因为我不熟悉 patch/mock 等较新的功能。 sorens没有建议一种方法来使示例代码的TestStdIO类的自定义断言方法在不借助剪切/粘贴的情况下可重用,因此我采用了使TestStdIO成为在其自己的模块中定义的“mixin”类的方法( teststdoutmethods.py中下面的例子)。 由于通常在 TestStdIO 中使用的unittest.TestCase提供的断言方法引用也将在测试用例类中可用,因此我从他的示例代码中删除了import unittest行,并在类声明中从unittest.TestCase中删除了TestStdIO的派生,即,
import io
import sys
class TestStdIO(object):
def setUp(self):
...
否则,TestStdIO 的代码与 sorens 的版本一样,最后没有两个示例用法。 我在 Ch 中的一个基本示例文本游戏中的一个类的一些简单单元测试测试用例中使用了TestStdIO的这个混合类版本。 Kinsley 和 McGugan 的Beginning Python Game Programming with PyGame中的 2 个,例如
import unittest
from teststdoutmethods import TestStdIO # sorens' TestStdIO as a mixin.
from tank import Tank # From Beginning Python Game Programming with PyGame.
class Test_Tank_fire(TestStdIO, unittest.TestCase): # Note multiple inheritance.
def test_Tank_fire_wAmmo(self):
oTank1 = Tank('Bill', 5, 100)
oTank2 = Tank('Jim', 5, 100)
self.setUp()
oTank1.fire_at(oTank2)
self.assertStdoutEquals("Bill fires on Jim\nJim is hit!")
self.assertEqual(str(oTank1), 'Bill (100 Armor, 4 Ammo)', 'fire_at shooter attribute results incorrect')
self.assertTrue(str(oTank2) == 'Jim (80 Armor, 5 Ammo)', 'fire_at target attribute results incorrect')
self.tearDown()
def test_Tank_fire_woAmmo(self):
oTank1 = Tank('Bill', 5, 100)
oTank2 = Tank('Jim', 5, 100)
# Use up 5 allotted shots.
for n in range(5):
oTank1.fire_at(oTank2)
self.setUp()
# Try one more.
oTank1.fire_at(oTank2)
self.assertStdoutEquals("Bill has no shells!")
self.tearDown()
def test_Tank_explode(self):
oTank1 = Tank('Bill', 5, 100)
oTank2 = Tank('Jim', 5, 100)
# Use up 4 shots.
for n in range(4):
oTank1.fire_at(oTank2)
self.setUp()
# Fifth shot should finish the target.
oTank1.fire_at(oTank2)
self.assertStdoutEquals("Bill fires on Jim\nJim is hit!\nJim explodes!")
self.tearDown()
self.assertTrue(str(oTank2) == 'Jim (DEAD)', 'fire_at target __str__ incorrect when Dead')
测试用例(成功和失败)在 Python 3.7 中运行。 请注意, sorens 的技术捕获了 setup() 和 teardown() 调用之间的所有标准输出输出,因此我将这些放在将生成我想要检查的特定输出的特定操作周围。 我认为我的 mixin 方法是sorens打算用于一般重用的方法,但我想知道是否有人有不同的建议。 谢谢。 [1]: https ://stackoverflow.com/a/62429695/7386731
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.