2011-02-01 11 views
5

Jestem równiarka dla początkujących klasy programowania przy użyciu Python. Mój python-fu nie jest zbyt silny, ale chciałbym zautomatyzować niektóre z ocen.Jak testować programy Pythona dla początkujących studentów używające input() (może z unittest?)?

Patrząc online, podoba mi się zestaw testowy PyUnit, choć prawdopodobnie jest trochę obezwładniony, jeśli chcę.

Moim problemem jest to, że nie wiem, jak przekazać dane wejściowe testowe do funkcji ucznia, ponieważ nie używają one argumentów wiersza poleceń lub nawet wielu funkcji, ale wprowadzają dane użytkownika za pomocą funkcji input().

Głupi przykład:

#/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() 

Na mój głupi przykład, w jaki sposób napisać badanej jednostki, które mogłyby sprawdzić wyjście dla kilku różnych wejść? (jeśli przejdę 2 i 3 do wejścia, wyjściowy ciąg powinien wynosić "Suma to 5")

+0

W dwóch wierszach wejściowych brakuje prawego nawiasu. – Javier

+0

@Javier: Naprawiono. Dzięki, ktoś zredagował moje pytanie i dodał 'eval (' ale nie zamknąłem drugiej strony.) – Jason

Odpowiedz

8

Edycja: Proponuje się tylko to, ponieważ przykład nie jest niemożliwy do wykonania (i zakładam, że początkujący studenci po prostu będą zdezorientowani przez ograniczenie)

Jeśli interesuje Cię tylko wynik pasujący do tego, czego szukasz, dlaczego nie po prostu użyć jakiegoś "głupiego" basha? Coś jak:

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

Jeśli robisz programy stosunkowo trywialne jak ten, to powinno być wystarczające, lub na tyle dobrze, że rozwiązanie wymaga trochę wysiłku.

+0

To jest dla mnie najbardziej przydatne. Masz rację, że tworzenie testowalnego programu nie leży w tym, czego uczono uczniów (to jest BARDZO wstępna klasa poziomu), ale coś takiego byłoby w rzeczywistości najlepsze – Jason

+0

@Jason - Powinienem przeczytać to pytanie jeszcze pełniej, zanim odpowiem. * westchnienie * – Omnifarious

4

Naprawdę nie możesz tego sprawdzić. Jedną z rzeczy związanych z pisaniem testów jednostkowych jest to, że często trzeba napisać swój kod inaczej, aby umożliwić testowanie jednostkowe. Tak więc w tym przypadku trzeba by było wziąć pod uwagę wywołania, aby wprowadzić osobną funkcję, którą następnie można załączyć.

def my_input(prompt): 
    return input(prompt) 

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

itd. Teraz twój test może małpa-łata myscript.my_input wrócić żądane wartości.

+0

Dziękuję za wyjaśnienia. Robienie czegoś takiego w celach testowych dużo sensu w kontekście tego, co przeczytałem. – Jason

2

Jeśli potrzebujesz bardziej skomplikowanej interakcji z programami wiersza poleceń, niż można uzyskać z echo, możesz spojrzeć na expect.

2

Z docs:

Obiekty sys.stdin, sys.stdout i sys.stderr są inicjowane złożyć obiekty odpowiadające standardowego wejścia tłumacza, wyjście i strumieni o błędach.

Tak postępując zgodnie z tą logiką, coś podobnego wydaje się działać.Utwórz plik z wymagany nakład:

$ cat sample_stdin.txt 
hello 
world 

Następnie przekierować sys.stdin pkt do tego pliku:

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

wyjściowa:

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

Krótka odpowiedź, nie rób tego. Musisz zaprojektować testowalność. Oznacza to zapewnienie prostego sposobu dostarczania interfejsów dla rzeczy używanych do rozmowy z zasobami systemowymi, dzięki czemu można zapewnić alternatywne implementacje tych interfejsów podczas testowania.

Rozwiązanie poprawki małpy opisane w drugiej odpowiedzi działa, ale jest to najbardziej prymitywne z dostępnych opcji. Osobiście napisałbym klasę interfejsu dla interakcji użytkownika. Na przykład:

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

Następnie rzeczy, które wymagają rozmowy z użytkownikiem, mogą uzyskać instancję klasy jako argument konstruktora lub funkcji. Domyślna implementacja może wywoływać rzeczywistą funkcję input lub inną, ale istnieje wersja używana do testowania, która zapewnia próbkę wejścia lub buforuje wyjście, aby można było je sprawdzić.

To, przy okazji, dlatego nienawidzę Singleton (którego tak naprawdę nie można skutecznie wdrożyć w Pythonie). Niszczy Twoją zdolność do testowania poprzez stworzenie globalnie dostępnej instancji, której nie można zgasić za pomocą wersji pośredniczącej do testowania.

0

Być może uda Ci się sfałszować funkcję input, aby dostarczyć dane wejściowe ze środowiska testowego.

Wygląda na to, że to może zadziałać. Jest nietestowany.

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

Zamień sys.stdin na obiekt StringIO (lub cStringIO).

2

Moja propozycja jest taka byłaby kodu za pomocą jednego z dwóch ram, że Python przewiduje testów jednostkowych: unittest (aka PyUnit) i doctest.

To jest przykład użycia 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() 

I jest przykładem pomocą 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() 

z doctest że wykonany inny przykład za pomocą wejścia() (Zakładam, że używasz Pythona 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() 

chciałbym uruchomić każdy z wyżej wymienionych przykładów z opcją -v:

python adder_ugly.py -v 

Dla Patrz odnośnik:

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

i

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