测试Python脚本

non*_*ero 11 python tdd unit-testing

如何使用doctest,unittest,nose等测试框架测试Python脚本的STDOUT输出?例如,假设运行我的脚本"todo.py --list"应该返回"取出垃圾".我读过有人将脚本的STDOUT打印部分与生成要打印的输出的部分分开.我习惯在我的shell脚本周围喷洒打印语句.这只是一个TDD不友好的习惯我应该打破还是有办法轻松测试正确的打印输出?

小智 10

我看到两种方式:

  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"
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用记录器输出并在测试中收听.


nco*_*lan 7

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")
    
    Run Code Online (Sandbox Code Playgroud)
  2. 使用该subprocess模块.当我们特别想要测试命令行参数的处理时,我们使用它.有关几个示例,请参见http://hg.python.org/cpython/file/default/Lib/test/test_cmd_line_script.py.


Mic*_*lon 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"
Run Code Online (Sandbox Code Playgroud)

这是正在测试的脚本 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
Run Code Online (Sandbox Code Playgroud)