Как да тествам while-loop (веднъж) с nosetest (Python 2.7)

Аз съм доста нов в цялото това „нещо с програмирането“, но на 34 години си помислих, че бих искал да науча основите. За съжаление не познавам програмисти на Python. Уча програмиране поради личен интерес (и все повече и повече за забавление), но моето "социално местообитание" не е "където програмистите бродят" ;) . Почти приключих със Zed Shaws „Научете Python по трудния начин“ и за първи път не мога да намеря решение на проблем. През последните два дни дори не попаднах на полезни съвети къде да търся, когато многократно перифразирах (и потърсих) въпроса си. Така че stackoverflow изглежда е правилното място. Между другото: Липсва ми и правилният речник доста често, така че, моля, не се колебайте да ме поправите :) . Това може да е една от причините да не мога да намеря отговор. Използвам Python 2.7 и nosetests.

Доколко реших проблема (мисля) в стъпките, които го реших:

Функция 1:

def inp_1():
    s = raw_input(">>> ")
    return s

Всички тестове импортират следното, за да могат да правят нещата по-долу:

from nose.tools import *
import sys
from StringIO import StringIO
from mock import *
import __builtin__
# and of course the module with the functions

Ето теста за inp_1:

import __builtin__
from mock import *

def test_inp_1():
    __builtin__.raw_input = Mock(return_value="foo")
    assert_equal(inp_1(), 'foo')

Тази функция/тест е ок.

Доста подобна е следната функция 2:

def inp_2():
    s = raw_input(">>> ")
    if s == '1':
        return s
    else:
        print "wrong"

Тест:

def test_inp_2():
    __builtin__.raw_input = Mock(return_value="1")
    assert_equal(inp_1(), '1')

    __builtin__.raw_input = Mock(return_value="foo")
    out = StringIO()
    sys.stdout = out
    inp_1()
    output = out.getvalue().strip()
    assert_equal(output, 'wrong')

Тази функция/тест също е ок.

Моля, не предполагайте, че наистина знам какво се случва „зад кулисите“, когато използвам всички неща по-горе. Имам някои неспециализирани обяснения как всичко това функционира и защо получавам резултатите, които искам, но също така имам чувството, че тези обяснения може да не са напълно верни. Няма да е първият път, когато мисля нещо. работите се оказват различни, след като научих повече. Особено всичко с "__" ме обърква и ме е страх да го използвам, тъй като наистина не разбирам какво се случва. Както и да е, сега аз "просто" искам да добавя while-цикъл, за да поискам въвеждане, докато не е правилно:

def inp_3():
    while True:
        s = raw_input(">>> ")
        if s == '1':
            return s
        else:
            print "wrong"

Тестът за inp_3 мислех, че ще бъде същият като за inp_2. Поне не получавам съобщения за грешка. Но резултатът е следният:

$ nosetests
......

     # <- Here I press ENTER to provoke a reaction
     # Nothing is happening though.

^C   # <- Keyboard interrupt (is this the correct word for it?)
----------------------------------------------------------------------
Ran 7 tests in 5.464s

OK
$ 

Останалите 7 теста са sth. иначе (и добре). Тестът за inp_3 ще бъде тест №. 8. Времето е само времето, изминало докато натисна CTRL-C. Не разбирам защо не получавам съобщения за грешка или „неуспешен тест“, а само „ОК“.

Така че освен факта, че може да успеете да посочите лош синтаксис и други неща, които могат да бъдат подобрени (наистина бих го оценил, ако направите това), въпросът ми е:

Как мога да тествам и прекъсна цикли while с nosetest?


person Tentacel    schedule 28.11.2014    source източник
comment
Ако искате да говорите с разработчиците на Python, винаги можете да чатите в чат стаята на python!   -  person HarryCBurn    schedule 28.11.2014


Отговори (2)


И така, проблемът тук е, когато извикате inp_3 в тест за втори път, докато се подигравате на raw_input с Mock(return_value="foo"). Вашата функция inp_3 изпълнява безкраен цикъл (while True) и вие не я прекъсвате по никакъв начин, освен за условието if s == '1'. Така че с Mock(return_value="foo") това условие никога не е удовлетворено и вашият цикъл продължава да работи, докато не го прекъснете с външни средства (Ctrl + C във вашия пример). Ако това е умишлено поведение, тогава Как да ограничим изпълнението време на извикване на функция в Python ще ви помогне да ограничите времето за изпълнение на inp_3 в теста. Въпреки това, в случаите на въвеждане като във вашия пример, разработчиците често прилагат ограничение за броя опити за въвеждане, които потребителят има. Можете да го направите с помощта на променлива за преброяване на опитите и когато достигне максимума, цикълът трябва да бъде спрян.

def inp_3():
    max_attempts = 5
    attempts = 0
    while True:
        s = raw_input(">>> ")
        attempts += 1 # this is equal to "attempts = attempts + 1"
        if s == '1':
            return s
        else:
            print "wrong"
            if attempts == max_attempts:
                print "Max attempts used, stopping."
                break # this is used to stop loop execution
                # and go to next instruction after loop block

    print "Stopped."

Също така, за да научите Python, мога да препоръчам книгата "Learning Python" от Марк Луц. Обяснява до голяма степен основите на Python.

АКТУАЛИЗАЦИЯ:

Не можах да намеря начин да се подигравам на True на python (или builtin.True) (и да, това звучи малко налудничаво), изглежда, че python не ми позволи (и няма да ми позволи) да го направя. Въпреки това, за да постигнете точно това, което желаете, да стартирате безкраен цикъл веднъж, можете да използвате малък хак.

Дефинирайте функция, която да връща True

def true_func():
    return True

, използвайте го в цикъла while

while true_func():

и след това го подигравайте в тест с такава логика:

def true_once():
    yield True
    yield False


class MockTrueFunc(object):
    def __init__(self):
        self.gen = true_once()

    def __call__(self):
        return self.gen.next()

След това в теста:

true_func = MockTrueFunc()

С това вашият цикъл ще се изпълнява само веднъж. Тази конструкция обаче използва няколко усъвършенствани трика на Python, като генератори, "__" методи и т.н. Така че я използвайте внимателно.

Но така или иначе, като цяло безкрайните цикли се считат за лоши дизайнерски решения. По-добре да не се свиква :).

person pavel_form    schedule 28.11.2014

Винаги е важно да ми напомняте, че безкрайните цикли са лоши. Така че благодаря за това и още повече за краткия пример как да го направим по-добър. Ще го правя, когато е възможно.

Въпреки това, в действителната програма безкрайният цикъл е начинът, по който бих искал да го направя този път. Кодът тук е просто опростеният проблем. Много оценявам идеята ви с модифицираната "вярна функция". Никога не бих си помислил за това и по този начин научих нов "метод" за справяне с проблеми с програмирането :) . Все още не е начинът, по който бих искал да го направя този път, но това беше толкова важната следа, от която се нуждаех, за да разреша проблема си със съществуващите методи. Никога не бих си помислил да върна различна стойност втория път, когато извикам същия метод. Толкова е просто и брилянтно, че ме изумява :).

Mock-module има някои функции, които позволяват връщането на различна стойност при всяко извикване на mocked метода - страничен ефект .

side_effect може също да бъде зададен на […] итерируем. [когато] вашият макет ще бъде извикан няколко пъти и искате всяко извикване да връща различна стойност. Когато зададете side_effect на iterable, всяко извикване на mock връща следващата стойност от iterable:

Цикълът while ИМА "изход" (това ли е правилният термин за него?). Просто се нуждае от "1" като вход. Ще използвам това, за да изляза от цикъла.

def test_inp_3():
    # Test if input is correct
    __builtin__.raw_input = Mock(return_value="1")
    assert_equal(inp_1(), '1')

    # Test if output is correct if input is correct two times.
    # The third time the input is corrct to exit the loop.
    __builtin__.raw_input = Mock(side_effect=['foo', 'bar', '1'])
    out = StringIO()
    sys.stdout = out
    inp_3()
    output = out.getvalue().strip()

    # Make sure to compare as many times as the loop 
    # is "used".
    assert_equal(output, 'wrong\nwrong')

Сега тестът се изпълнява и връща "ok" или грешка, напр. ако първият вход вече излиза от цикъла.

Много ви благодаря отново за помощта. Това оправи деня ми :)

person Tentacel    schedule 30.11.2014