2011-02-01 9 views
5

Sono un grader per una classe di programmazione iniziale che utilizza Python. Anche il mio python-fu non è così forte, ma mi piacerebbe provare ad automatizzare alcuni dei voti.Come posso testare i programmi Python per principianti che usano input() (magari con unittest?)?

Guardando online, mi piace la suite di test PyUnit, anche se probabilmente è un po 'sopraffatta per quello che voglio.

mio problema è che non sono sicuro di come passare gli ingressi di prova che voglio funzioni dello studente, dal momento che non stanno usando argomenti della riga di comando o funzioni ancora più ancora, ma l'input dell'utente attraverso la funzione input().

uno stupido esempio:

#/usr/bin/python3.1 
# A silly example python program 
def main(): 
    a = int(input("Enter an integer: ")) 
    b = int(input("Enter another integer: ")) 
    c = a+b 
    print("The sum is %d" % c) 

if __name__ == '__main__' 
    main() 

Per il mio esempio stupido, come vorrei scrivere uno unit test che potrebbe controllare l'output per più ingressi differenti? (cioè, se passo 2 e 3 all'ingresso, la stringa di output dovrebbe essere "La somma è 5")

+0

Le due righe di input mancano di una parentesi chiusa. – Javier

+0

@ Javier: corretto. Grazie, qualcuno ha modificato la mia domanda e aggiunto 'eval (' ma non ha chiuso l'altro lato. – Jason

risposta

8

Modifica: solo proponendo questo dall'esempio non è inagibile (e sto assumendo il gli studenti principianti saranno semplicemente confusi dal vincolo)

Se si è solo preoccupati dell'output corrispondente a quello che si sta cercando, perché non usare solo una "stupida" bash? Qualcosa di simile:

echo -e "2\n3" | python test.py | grep -q "The sum is 5" && echo "Success" 

Se si sta facendo programmi relativamente banali come questo, allora questo dovrebbe essere una soluzione sufficiente, o abbastanza bene, che richiede poco sforzo.

+0

Questo è in realtà più utile per me. Hai ragione che fare un programma testabile non è all'interno di ciò che gli studenti hanno insegnato (è una classe di livello MOLTO INTRO), ma qualcosa del genere sarebbe davvero meglio – Jason

+0

@Jason - Dovrei leggere la domanda più completamente prima di rispondere. * sigh * – Omnifarious

4

Non è possibile testare l'unità. Una delle cose riguardo alla scrittura dei test unitari è che spesso devi scrivere il tuo codice in modo diverso per permetterne il test unitario. Pertanto, in questo caso, è necessario calcolare le chiamate da immettere in una funzione separata, che è possibile applicare successivamente.

def my_input(prompt): 
    return input(prompt) 

def main(): 
    a = int(eval(my_input("Enter an integer: ")) 

ecc Ora il vostro test può scimmia-patch myscript.my_input per restituire i valori desiderati.

+0

Grazie per i chiarimenti. Fare qualcosa come questo a scopo di test rende molto senso nel contesto di ciò che avevo letto. – Jason

2

Se è necessaria un'interazione più complicata con i programmi della riga di comando che può essere fornita con echo, è possibile che si desideri esaminare expect.

2

Dal docs:

Gli oggetti sys.stdin, sys.stdout e sys.stderr sono inizializzate a depositare oggetti corrispondenti a standard input interprete, uscita e flussi di errore.

Quindi seguendo questa logica, qualcosa del genere sembra funzionare.Creare un file con il vostro ingresso richiesto:

$ cat sample_stdin.txt 
hello 
world 

quindi reindirizzare sys.stdin per puntare a quel file:

#!/usr/bin/env python 
import sys 

fh = open('sample_stdin.txt', 'r') 
sys.stdin = fh 

line1 = raw_input('foo: ') 
line2 = raw_input('bar: ') 

print line1 
print line2 

uscita:

$python redirecting_stdin.py 
foo: bar: hello 
world 
2

Risposta breve, non lo facciamo. Devi progettare per testabilità. Ciò significa fornire un modo semplice per fornire interfacce per le cose da utilizzare per comunicare con le risorse di sistema in modo da poter fornire implementazioni alternative di tali interfacce durante il test.

La soluzione di patching scimmia descritta nell'altra risposta funziona, ma è la più primitiva delle opzioni. Personalmente, scriverei una classe di interfaccia per l'interazione dell'utente. Per esempio:

class UserInteraction(object): 
    def get_input(self): 
     raise NotImplementedError() 
    def send_output(self, output): 
     raise NotImplementedError() 

Poi le cose che hanno bisogno di parlare con l'utente può ottenere un'istanza della classe come argomento del costruttore o una funzione. L'implementazione predefinita può chiamare la funzione effettiva input o qualsiasi altra cosa, ma esiste una versione utilizzata per i test che fornisce input di esempio o buffer sull'output in modo che possa essere controllato.

Questo, per inciso, è il motivo per cui odio Singleton (che comunque non può essere effettivamente implementato in Python). Distrugge la tua capacità di test creando un'istanza accessibile a livello globale che non può essere eliminata con una versione stub per il test.

0

Potrebbe essere possibile simulare la funzione input per fornire input dall'ambiente di test.

Sembra che potrebbe funzionare. Non è stato testato.

class MockInput(object): 
    def __init__(self, *values): 
     self.values= list(values) 
     self.history= [] 
    def __call__(self, *args, **kw): 
     try: 
      response= self.values.pop(0) 
      self.history.append((args, kw, response)) 
      return response 
     except IndexError: 
      raise EOFError() 

class TestSomething(unittest.TestCase): 
    def test_when_input_invalid(self): 
     input= MockInput("this", "and", "that") 
     # some test case based on the input function 
0

Sostituire sys.stdin con un oggetto StringIO (o cStringIO).

2

Il mio suggerimento è quello di refactoring il codice utilizzando uno dei due quadri che Python fornisce per unit test: unittest (aka PyUnit) e doctest.

Questo è un esempio usando unittest:

import unittest 

def adder(a, b): 
    "Return the sum of two numbers as int" 
    return int(a) + int(b) 

class TestAdder(unittest.TestCase): 
    "Testing adder() with two int" 
    def test_adder_int(self): 
     self.assertEqual(adder(2,3), 5) 

    "Testing adder() with two float" 
    def test_adder_float(self): 
     self.assertEqual(adder(2.0, 3.0), 5) 

    "Testing adder() with two str - lucky case" 
    def test_adder_str_lucky(self): 
     self.assertEqual(adder('4', '1'), 5) 

    "Testing adder() with two str" 
    def test_adder_str(self): 
     self.assertRaises(ValueError, adder, 'x', 'y') 

if __name__ == '__main__': 
    unittest.main() 

E questo è un esempio usando doctest:

# adder.py 

def main(a, b): 
    """This program calculate the sum of two numbers. 
    It prints an int (see %d in print()) 

    >>> main(2, 3) 
    The sum is 5 

    >>> main(3, 2) 
    The sum is 5 

    >>> main(2.0, 3) 
    The sum is 5 

    >>> main(2.0, 3.0) 
    The sum is 5 

    >>> main('2', '3') 
    Traceback (most recent call last): 
     ... 
    TypeError: %d format: a number is required, not str 
    """ 
    c = a + b 
    print("The sum is %d" % c) 

def _test(): 
    import doctest, adder 
    return doctest.testmod(adder) 

if __name__ == '__main__': 
    _test() 

Con doctest feci altro esempio utilizzando il metodo() (Presumo che tu stia usando Python 3.X):

# adder_ugly.py 

def main(): 
    """This program calculate the sum of two numbers. 
    It prints an int (see %d in print()) 

    >>> main() 
    The sum is 5 
    """ 
    a = int(input("Enter an integer: ")) 
    b = int(input("Enter another integer: ")) 
    c = a+b 
    print("The sum is %d" % c) 


def _test(): 
    import doctest, adder_ugly 
    return doctest.testmod(adder_ugly) 

if __name__ == '__main__': 
    _test() 

vorrei correre ciascuno degli esempi con l'opzione -v di cui sopra:

python adder_ugly.py -v 

Per la vostra sede di riferimento:

http://docs.python.org/py3k/library/unittest.html?highlight=unittest#unittest

e

http://docs.python.org/py3k/library/doctest.html#module-doctest