2011-05-12 423 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