2

I'm wondering what is the best way to unittest a while loop that breaks only when a specific input is entered (see code for more detail). My test actually works as intended and fails correctly. However, I feel like it's 'hacked together' and isn't the best way to test this kind of code.

Currently, I have to manually break the loop within the test by mocking an user input that breaks the loop (see code for more detail). I've tried you parametrize in pytest, however, the test gets stuck on an infinite loop as soon as I mock an input that doesn't break the loop. It never gets to the next parametrized value.

Function Being Tested

def export_options():
    while True:
        try:
            choice = int(input("\nPlease make a selection"))
            if choice in range(1, 5):
                return choice
            else:
                 # I want to test this line when the input is bad
                print("\nNot a valid selection\n")  
        except ValueError as err:
            print("Please enter an integer")

Test Function

@mock.patch('realestate.app.user_inputs.print')
# The 1 is used to break the loop otherwise the test never ends!
@mock.patch('realestate.app.user_inputs.input', side_effect=[0, 5, 1])
def test_export_options_invalid_integer(mock_choice, mock_print):
    user_inputs.export_options()

    # Best way I could think of to test bad inputs. Anything that is less than 1 or greater than 4 will fail the test.
    # Make sure I call the correct print function
    mock_print.assert_called_with("\nNot a valid selection\n")
    # Make sure I call the correct print function twice
    assert mock_print.call_count == 2

I get the results I want based on my current code. However, I'd like to use best practices when possible and apply them to all future tests when dealing with while loops that only break based on a specific user input.

1 Answer 1

2

Regarding "real" infinite loops that never exit: Processes on embedded systems are sometimes implemented like this, and will terminate only when the system shuts down by power-off or by getting killed by the operating system. For such loops the typical solution is to extract the loop body into a separate function/method.

def main_loop():
    while True:
        main_body()

This way, you have maximized the amount of code that can be unit-tested by extracting it to main_body. Clearly, main_loop can still not be unit-tested, but it can be tested in higher-level tests.

Obviously, your loop is not of that kind since you have a possibility to leave the loop without killing threads or the like. Therefore, I would argue that your approach to testing that function is quite sound in principle.

There are some ways to improve the test suite, however. To give some example: The test case you have shown as an example is already a complex test, and I would recommend to start with more fundamental tests:

  • Test that the number 1 is accepted
  • Test that the number 4 is accepted
  • Test that the number 0 is not accepted (sequence: 0, one from 1..4)
  • Test that the number 5 is not accepted (sequence: 5, one from 1..4)

If any of these tests fail, it will give you a more direct indication where the problem could lie. What these tests do not yet test (in contrast to your test) is, whether the routine will be able to handle more than one bad input, and, whether the prompts and error messages are as expected. These are, however, individual aspects and thus it would make sense to split your test into separate tests, one test per aspect.

Sign up to request clarification or add additional context in comments.

3 Comments

Hi thank you for your feedback! I should add that I test 1 through 4 with parametrize so all the valid inputs are returned as intended (function not included here). As for points 3 and 4, is the way I tested 0 and 5 not that sound? Like using the side effect to make two calls and seeing if the print statement was called only twice?
In other words, is it always better to test each variable separately rather than the way I did? I figured it would look "cleaner" than having two separate tests to check two invalid values.
Sorry if I was not clear: Having a test (as yours) that tests that more than one bad input can be given is perfectly fine. What I meant is, that this test ideally follows some tests that check more fundamental properties of the code. For example, if the test fails that checks if 1 is an accepted input, then a more complex test will likely also fail. When you then look at the test results you will realize that the fundamental test failed and you may immediately see the issue. If you only have the complex test, you may not see immediately what caused the failure.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.