The Python Oracle

How to test while-loop (once) with nosetest (Python 2.7)

--------------------------------------------------
Hire the world's top talent on demand or became one of them at Toptal: https://topt.al/25cXVn
--------------------------------------------------

Music by Eric Matyas
https://www.soundimage.org
Track title: Hypnotic Orient Looping

--

Chapters
00:00 How To Test While-Loop (Once) With Nosetest (Python 2.7)
03:07 Accepted Answer Score 3
05:09 Answer 2 Score 1
06:49 Thank you

--

Full question
https://stackoverflow.com/questions/2719...

--

Content licensed under CC BY-SA
https://meta.stackexchange.com/help/lice...

--

Tags
#python #python27 #nose

#avk47



ACCEPTED ANSWER

Score 3


So, the problem here is when you call inp_3 in test for second time, while mocking raw_input with Mock(return_value="foo"). Your inp_3 function runs infinite loop (while True) , and you're not interrupting it in any way except for if s == '1' condition. So with Mock(return_value="foo") that condition is never satisfied, and you loop keeps running until you interrupt it with outer means (Ctrl + C in your example). If it's intentional behavior, then How to limit execution time of a function call in Python will help you to limit execution time of inp_3 in test. However, in cases of input like in your example, developers often implement a limit to how many input attempts user have. You can do it with using variable to count attempts and when it reaches max, loop should be stopped.

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."

Also, to learn python I can recommend book "Learning Python" by Mark Lutz. It greatly explains basics of python.

UPDATE:

I couldn't find a way to mock python's True (or a builtin.True) (and yea, that sounds a bit crazy), looks like python didn't (and won't) allow me to do this. However, to achieve exactly what you desire, to run infinite loop once, you can use a little hack.

Define a function to return True

def true_func():
    return True

, use it in while loop

while true_func():

and then mock it in test with such logic:

def true_once():
    yield True
    yield False


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

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

Then in test:

true_func = MockTrueFunc()

With this your loop will run only once. However, this construction uses a few advanced python tricks, like generators, "__" methods etc. So use it carefully.

But anyway, generally infinite loops considered to be bad design solutions. Better to not getting used to it :).




ANSWER 2

Score 1


It's always important to remind me that infinite loops are bad. So thank you for that and even more so for the short example how to make it better. I will do that whenever possible.

However, in the actual program the infinite loop is how I'd like to do it this time. The code here is just the simplified problem. I very much appreciate your idea with the modified "true function". I never would have thought about that and thus I learned a new "method" how tackle programming problems :) . It is still not the way I would like to do it this time, but this was the so important clue I needed to solve my problem with existing methods. I never would have thought about returning a different value the 2nd time I call the same method. It's so simple and brilliant it's astonishing me :).

The mock-module has some features that allows a different value to be returned each time the mocked method is called - side effect .

side_effect can also be set to […] an iterable. [when] your mock is going to be called several times, and you want each call to return a different value. When you set side_effect to an iterable every call to the mock returns the next value from the iterable:

The while-loop HAS an "exit" (is this the correct term for it?). It just needs the '1' as input. I will use this to exit the loop.

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')

Now the test runs and returns "ok" or an error e.g. if the first input already exits the loop.

Thank you very much again for the help. That made my day :)