如何使用doctest,unittest,nose等测试框架测试Python脚本的STDOUT输出?例如,假设运行我的脚本“todo.py --list”应该返回“取出垃圾”。我读过有人将脚本的STDOUT打印部分与生成要打印的输出的部分分开。我习惯在我的shell脚本周围喷洒打印语句。这只是一个TDD不友好的习惯我应该破解还是有办法轻松测试正确的打印输出?
答案 0 :(得分:10)
我看到两种方式:
在单元测试期间重定向标准输出:
class YourTest(TestCase):
def setUp(self):
self.output = StringIO()
self.saved_stdout = sys.stdout
sys.stdout = self.output
def tearDown(self):
self.output.close()
sys.stdout = self.saved_stdout
def testYourScript(self):
yourscriptmodule.main()
assert self.output.getvalue() == "My expected ouput"
使用记录器输出并在测试中收听。
答案 1 :(得分:7)
Python自己的测试套件可以做到这一点,我们使用两种主要技术:
重定向标准输出(正如其他人所建议的那样)。我们使用上下文管理器:
import io
import sys
import contextlib
@contextlib.contextmanager
def captured_output(stream_name):
"""Run the 'with' statement body using a StringIO object in place of a
specific attribute on the sys module.
Example use (with 'stream_name=stdout'):
with captured_stdout() as s:
print("hello")
assert s.getvalue() == "hello"
"""
orig_stdout = getattr(sys, stream_name)
setattr(sys, stream_name, io.StringIO())
try:
yield getattr(sys, stream_name)
finally:
setattr(sys, stream_name, orig_stdout)
def captured_stdout():
return captured_output("stdout")
def captured_stderr():
return captured_output("stderr")
def captured_stdin():
return captured_output("stdin")
使用subprocess
模块。当我们特别想要测试命令行参数的处理时,我们使用它。有关几个示例,请参阅http://hg.python.org/cpython/file/default/Lib/test/test_cmd_line_script.py。
答案 2 :(得分:3)
当你使用py.test进行测试时。您可以使用“capsys”或“capfd”测试函数参数来对STDOUT和STDIN运行断言
def test_myoutput(capsys): # or use "capfd" for fd-level
print ("hello")
sys.stderr.write("world\n")
out, err = capsys.readouterr()
assert out == "hello\n"
assert err == "world\n"
print "next"
out, err = capsys.readouterr()
assert out == "next\n"
可以找到更多详细信息in the py.test docs
答案 3 :(得分:1)
这是我在一天晚上编写的测试脚本运行的内容。请注意,测试确实涵盖了基本情况,但它不够彻底,无法单独进行单元测试。考虑它是初稿。
import sys
import subprocess
if sys.platform == "win32":
cmd = "zs.py"
else:
cmd = "./zs.py"
def testrun(cmdline):
try:
retcode = subprocess.call(cmdline, shell=True)
if retcode < 0:
print >>sys.stderr, "Child was terminated by signal", -retcode
else:
return retcode
except OSError, e:
return e
tests = []
tests.append( (0, " string pattern 4") )
tests.append( (1, " string pattern") )
tests.append( (3, " string pattern notanumber") )
passed = 0
for t in tests:
r = testrun(cmd + t[1])
if r == t[0]:
res = "passed"
passed += 1
else:
res = "FAILED"
print res, r, t[1]
print
if passed != len(tests):
print "only",passed,"tests passed"
else:
print "all tests passed"
这是正在测试的脚本,zs.py,这样可以在字符串中进行模式搜索,类似于生物化学家在DNA数据或蛋白质链数据中搜索模式的方式。
#!/usr/bin/env python
# zs - some example Python code to demonstrate to Z??s
# interviewers that the writer really does know Python
import sys
from itertools import *
usage = '''
Usage: zs <string> <pattern> <n>"
print top n matches of pattern in substring"
'''
if sys.hexversion > 0x03000000:
print "This script is only intended to run on Python version 2"
sys.exit(2)
if len(sys.argv) != 4:
print usage
sys.exit(1)
A = sys.argv[1] # string to be searched
B = sys.argv[2] # pattern being searched for
N = sys.argv[3] # number of matches to report
if not N.isdigit():
print "<n> must be a number"
print usage
sys.exit(3)
def matchscore(s1, s2):
''' a helper function to calculate the match score
'''
matches = 0
for i in xrange(len(s1)):
if s1[i] == s2[i]:
matches += 1
return (matches + 0.0) / len(s1) # added 0.0 to force floating point div
def slices(s, n):
''' this is a generator that returns the sequence of slices of
the input string s that are n characters long '''
slen = len(s)
for i in xrange(slen - n + 1):
yield s[i:i+n]
matchlen = len(B)
allscores = ((matchscore(x,B),x,i) for i,x in enumerate(slices(A,matchlen)))
nonzeros = [ y for y in allscores if y[0] != 0 ]
for elem in sorted(nonzeros,key=lambda e: e[0],reverse=True):
nprinted = 0 # We will count them; in case num elements > N
print elem[1], str(round(elem[0],4)), elem[2]
nprinted += 1
if nprinted >= N:
break
答案 4 :(得分:0)
我也想查看TextTest测试框架。它更侧重于功能/验收测试(因此不太适合单元测试),并且在很大程度上依赖于程序的文本输出。这样你的习惯就变得很好了: - )。