2011-05-12 224 views
9

如何使用像doctest,unittest,nose等测试框架测试Python脚本的STDOUT输出?例如,说运行我的脚本“todo.py --list”应该返回“取出垃圾”。我读过一些人,他将脚本的STDOUT打印部分从生成要打印的输出的部分中分离出来。我习惯在我的shell脚本周围喷洒打印语句。这是否仅仅是TDD不友好的习惯,我应该打破,还是有办法轻松测试正确的打印输出吗?测试Python脚本

+1

doctest已经知道如何处理输出... – 2011-05-12 07:18:09

+0

首先。搜索:http://stackoverflow.com/questions/3481561/python-using-doctest-on-the-mainline。第二。搜索:doctest文档(http://docs.python.org/library/doctest.html#how-are-docstring-examples-recognized)说“输出到标准输出被捕获,但不输出到标准错误”。 – 2011-05-12 09:57:28

回答

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 
8

我看到有两种方式:

  1. 重定向的单元测试过程中标准输出:

    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" 
    
  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

0

我可能也想看看TextTest测试框架。它更侧重于功能/验收测试(因此不太适合单元测试)并且严重依赖于程序的文本输出。这样你的习惯就变成了一个好习惯:-)。

6

Python的测试套件做到这一点相当多,我们用两种主要的方法:

  1. 重定向标准输出(如其他人所说)。我们使用的上下文经理这样的:

    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") 
    
  2. 使用subprocess模块。当我们特别想测试命令行参数的处理时,我们使用它。几个例子见http://hg.python.org/cpython/file/default/Lib/test/test_cmd_line_script.py

+0

此代码不能在Python 2.7上运行。请参阅http://stackoverflow.com/questions/3423601/python-2-7-exec-what-is-wrong。我使用__module__ StringIO中的StringIO类来处理它。 – 2013-04-02 14:03:10