2011-05-12 11 views
9

¿Cómo puedo probar la salida STDOUT de una secuencia de comandos de Python con un marco de prueba como doctest, unittest, nose, etc.? Por ejemplo, digamos que ejecutar mi script "todo.py --list" debería devolver "take the garbage". Leí a alguien que separa la parte de impresión STDOUT del guión de la parte que genera el resultado que se imprimirá. Estoy acostumbrado a rociar las instrucciones de impresión alrededor de mis scripts de shell. ¿Es esto simplemente un hábito desagradable de TDD que debería romper o hay una manera de probar fácilmente la impresión correcta?Prueba de las secuencias de comandos de Python

+1

doctest ya sabe cómo manejar la salida ... –

+0

Primero. Búsqueda: http://stackoverflow.com/questions/3481561/python-using-doctest-on-the-mainline. Segundo. Buscar: la documentación más doctest (http://docs.python.org/library/doctest.html#how-are-docstring-examples-recognized) dice "La salida a stdout se captura, pero no a stderr". –

Respuesta

1

Aquí hay algo que escribí una tarde que prueba la secuencia de comandos. Tenga en cuenta que la prueba cubre los casos básicos, pero no es lo suficientemente minuciosa como para ser una prueba unitaria por sí misma. Considéralo un primer borrador.

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" 

Y aquí es el guión que se estaba probando, zs.py, esto búsquedas de patrones en una secuencia similar a la forma en la búsqueda bioquímicos patrones en datos de ADN o datos de la cadena de proteína.

#!/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

Veo dos maneras:

  1. Redirigir stdout durante el unittest:

    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. utilizar un registrador para sus salidas y escucharla en su prueba.

3

cuando utiliza py.test para su prueba. Puede utilizar los "CAPSYS" o los argumentos de la función de prueba "capfd" para ejecutar afirma contra STDOUT y 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" 

Más detalles se pueden encontrar in the py.test docs

0

Yo también puede que desee ver en el marco TextTest prueba . Se centra más en las pruebas funcionales/de aceptación (por lo que es menos susceptible a las pruebas unitarias) y se basa en gran medida en el resultado textual de un programa. De esta forma, tu hábito se convierte en uno bueno :-).

6

propio banco de pruebas de Python hace esto un poco, y se utilizan dos técnicas principales:

  1. Redirigir la salida estándar (como han sugerido otros). Utilizamos un gestor de contexto para esto:

    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. Con el módulo de subprocess. Usamos esto cuando específicamente queremos probar el manejo de los argumentos de línea de comando. Ver http://hg.python.org/cpython/file/default/Lib/test/test_cmd_line_script.py para varios ejemplos.

+0

Este código no se ejecuta en Python 2.7. Ver http://stackoverflow.com/questions/3423601/python-2-7-exec-what-is-wrong. Lo hice funcionar usando la clase StringIO del __module__ StringIO. –

Cuestiones relacionadas