Тестирование скриптов Python - PullRequest
10 голосов
/ 12 мая 2011

Как проверить вывод STDOUT скрипта Python с помощью инфраструктуры тестирования, такой как doctest, unittest, nose и т. Д.? Например, допустим, что при запуске моего скрипта «todo.py --list» должно появиться «вынести мусор». Я читал кого-то, кто отделяет часть сценария для печати STDOUT от части, которая генерирует вывод для печати. Я привык разбрасывать операторы печати вокруг моих сценариев оболочки. Это просто недружелюбная привычка TDD, которую я должен сломать, или есть способ легко проверить правильность вывода на печать?

Ответы [ 5 ]

10 голосов
/ 12 мая 2011

вижу два пути:

  1. Stdout перенаправления во время юнит-теста:

    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. Используйте регистратор для своих выходов и прослушайте его в своем тесте.

7 голосов
/ 12 мая 2011

Собственный набор тестов Python делает это совсем немного, и мы используем два основных метода:

  1. Перенаправление stdout (как предлагали другие).Для этого мы используем менеджер контекста:

    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 для нескольких примеров.

3 голосов
/ 12 мая 2011

когда вы используете 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"

Более подробную информацию можно найти в документации py.test

1 голос
/ 12 мая 2011

Вот кое-что, что я написал однажды вечером, когда запускается скрипт тестов. Обратите внимание, что тест охватывает основные случаи, но он недостаточно тщателен, чтобы быть единичным тестом. Считай это первым черновиком.

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, он выполняет поиск по шаблону в строке, аналогично тому, как биохимики ищут шаблоны по данным ДНК или белковой цепи.

#!/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
0 голосов
/ 12 мая 2011

Я также мог бы захотеть взглянуть на TextTest среду тестирования.В нем больше внимания уделяется функциональному / приемочному тестированию (поэтому он менее поддается модульному тестированию) и в значительной степени опирается на текстовый вывод программы.Таким образом, ваша привычка становится хорошей: -).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...