简体   繁体   中英

python testing, how handle 'continue' statement loop

I'm learning how to use Python. I have a function with a conditional inside of it, if an invalid input is provided, it should restart the loop until a valid input is provided.

Unfortunately, this "restarting" behavior is causing an infinite loop within my tests (it circularly provides the wrong input). How can I pause, or break, or limit the output to one instance so I can test the returned string?

function:

def confirm_user_choice(choice: str):
    while True:
        user_response = input(f"\nYou chose '{choice}', is this correct? y/n ")
        if user_response == "y":
            return True
        elif user_response == "n":
            return False
        else:
            print("\nSelect either 'y' (yes) or 'n' (no)")

test:

import unittest
from unittest import mock
from src.utils.utils import addValues, confirm_user_choice


class TestConfirmUserChoice(unittest.TestCase):
    def test_yes(self):
        with mock.patch("builtins.input", return_value="y"):
            result = confirm_user_choice("y")
        self.assertEqual(result, True)

    def test_no(self):
        with mock.patch("builtins.input", return_value="n"):
            result = confirm_user_choice("n")
        self.assertEqual(result, False)

    def test_invalid_input(self):
        with mock.patch("builtins.input", return_value="apple"):   <-- triggers func else case
            result = confirm_user_choice("apple")
        self.assertEqual(result, False)

You have a partial function: on a proper input, it will return a Boolean value, but it may not return at all , and you can't test that an infinite loop is indeed infinite.

To make it more testable, allow the function to take an optional iterable value that defaults to sys.stdin , allowing you to control what the function reads (and how long it will attempt to do so.)

def confirm_user_choice(choice: str, responses: Optional[Iterable[str]] = None):
    if responses is None:
        # An infinite stream of calls to input()
        responses = iter(lambda: input(f"\nYou chose '{choice}', is this correct? y/n "), None)

    for user_response in responses:
        if user_response == "y":
            return True
        elif user_response == "n":
            return False
        else:
            print("\nSelect either 'y' (yes) or 'n' (no)")
    else:
        # Note: cannot be raised from the default value of responses
        raise ValueError("Unexpected end of responses")

Now your test can simply pass canned lists of responses, and either catch the expected ValueError , or look at the returned Boolean value.

import unittest
from src.utils.utils import addValues, confirm_user_choice


class TestConfirmUserChoice(unittest.TestCase):
    def test_yes(self):
        result = confirm_user_choice("y", ["y"])
        self.assertTrue(result)

    def test_eventual_yes(self):
        result = confirm_user_choice("y", ["apple", "pear", "y"])
        self.assertTrue(result)

    def test_no(self):
        result = confirm_user_choice("y", ["n"])
        self.assertFalse(result)

    def test_no_valid_input(self):
        with self.assertRaises(ValueError):
            result = confirm_user_choice(["apple"])

continue does nothing in your code
continue alows you to ignore a part of the code for some instance of the loop.

For example:

for i in range(2):
   if i < 1:
      continue
   print(i)

Output:

1

For what you want to do, don't forget while is suppose to end when a condition is meet. Hence bypassing the condition using while True: and then using a if to exit your loop is a bit counter productive.

Just use the while condition:

user_response = ""
while user_response not in ["y", "n"]:
    user_response = input("y/n ? ")
print(user_response)

Happy programming

I'm new to Python myself but in my understanding, unit tests investigate how function handle different inputs based on the function's return value or exceptions raised (if any).

Your function only exits when the user inputs either "y" or "n" or when an error is raised (for instance, if the user provides Crtl-Z). Your while loop does not break when a user inputs 'apple.' There is no return value for pytest (or the like) to inspect.

If you really want to test this, you'd have to rewrite your function so that's a little more modular. It would have to feature at least three different return values, including one that implies that the input was invalid.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM