1

I'm a bit of a beginner in terms of unit-testing and still trying to get familiar with some of the things. This is part of my own project and I'm stuck on how to test it fully.

There was a question in this direction already, but it did only concern itself on how to repeatedly ask the user for input, not on how to unittest it.

Goal:

I have a function that asks the user for input and repeats the request if the input is invalid. My goal is to figure out how to test if the input-request is repeated if the user gives invalid input. By that I mean, I'm trying to test whether the mechanism to repeat the input-request under the defined circumstances works as intended.

The Code:

The function asks for a regular expression from the user and compiles it into an SRE_Pattern object from the re-package (python standard library). If the user provides an input and it's not a valid expression, the input-request is repeated.

import re
def request_regex_pattern(input_message):
 regex_pattern = None
 while True:
 regex = input(input_message)
 if not regex:
 print("No Regex provided.")
 break
 try:
 regex_pattern = re.compile(regex, re.IGNORECASE)
 break
 except re.error:
 print("The input was not valid regular expression")
 continue
 return regex_pattern

Tests so far:

What I can test so far is whether for valid input (e.g. \d\d) I get correct output (SRE_Patternobject of that regular expression) using mocking.

import unittest as ut
from unittest import mock
import re
class TestUserInput(ut.TestCase):
 def test_request_regex_pattern(self):
 with mock.patch('builtins.input', return_value='\d\d'):
 test_pattern = request_regex_pattern('')
 test_string = '01'
 self.assertIsNotNone(test_pattern.match(test_string))

I've thought about this and googled for a while now but couldn't come to a satisfying answer.

Is there a sensible way to test whether the input-request was repeated? What are the best-practices there?

Using python's default unittest library is not mandatory for solutions. However, generally solutions using the standard libraries would be preferred as that reduces the number of requirements needed for the project I'm working on.

Thank you very much for your time !

asked May 21, 2020 at 16:09
6
  • 1
    I have answered a somewhat similar question recently - please check if that helps you. Commented May 21, 2020 at 16:42
  • @MrBeanBremen Heya, thanks for the hint ! The problems do seem very similar and I think what you recommended there is most likely applicable here as well, but I do not quite grasp the code used for testing in neither the question nor the answer. I understand that similarly to my first test you patch the builtin input, however the syntax used for the patching there in both question and answer confuses me a fair bit. Commented May 21, 2020 at 17:01
  • @MrBeanBremen I think I grasped a bit of your answer there, is it that by patching the general print function to throw an Exception on the side while doing its actual thing, it triggers the "except" piece of the try-except statement of the first while-loop in main(), triggering a print of a string that only happens there, and with mock_print.assert_called_with() you can then assert the existence of ANY print call (since print in general was patched) with the string that only occurs in that particular "catch" bit ('You need to enter a number!') ? Commented May 21, 2020 at 17:06
  • 1
    Yes, you got that right. I can put together a more specific answer if needed, but it looks like your are getting this already :) Commented May 21, 2020 at 17:12
  • @MrBeanBremen Then thanks a lot for that hint ! I think I'll write up an answer tailored to this code based on the reply you're suggesting there. The difference in syntax in patching got me curious though (particularly considering the question of best practices / best readability). Would you be willing to comment a bit on under which circumstances one could prefer one over the other ? Commented May 21, 2020 at 17:15

2 Answers 2

2

MrBreanBremen pointed me in the right direction to linking me to his answer and kindly helped me with some of my follow-up questions regarding syntax and understanding the principle. I want to elaborate and dumb down the principles behind what he used there a bit for easier understanding.

The Principle

The way to check this is by patching a function that is called under the circumstances that you want to check. This replaces them with MagicMock-objects that know when they have been called and with which parameters !

Once the function is patched, you can then use that MagicMocks assert methods such as assert_called_with(), assert_called_once(), assert_called() and/or assert_not_called() to check whether the function that this MagicMock object replaced was called at all and with the parameters you were expecting.

Now how does this apply to this question?

First back to what is our problem. We have 3 Test-cases:

1) User provides valid regular expression that can compile into an SRE_Pattern object --> Returns an SRE_Pattern object

2) User provides no input (just hits enter) --> Returns None

3) User provide input that can't be compiled into an SRE_Pattern object (invalid input), triggering the print("The input was not valid regular expression") statement --> Never Returns anything

We only care about circumstances of 3), as case 1) and 2) have a defined output that we can check easily with "normal" unit-tests, while case 3) explicitly can't output anything, leading us to our problem.

As already hinted at in 3), under these circumstances only the print function is called. Meaning that for our test we should patch it, so that we can receive a MagicMock-object for this function and use its assert_called_with() together with the string that print-statement gets, as that string only occurs in that section of code. It is "unique" for these circumstances !

We have one more issue to solve though. Once we patch the builtins.input as before, but with something that causes our while-loop to repeat, we will still be stuck in a while-loop! The function call request_regex_pattern() will never end ! Since we can't exit the function normally, we'll have to exit by causing an Exception. Thus, we need to also patch that into one of the functions that is called when these circumstances happen. In this case, we can conveniently add this side-effect to our patch of print. We can then catch that Exception with a context-manager with self.assertRaises(Exception) to keep our test from failing.

The code:

import unittest as ut
from unittest import mock
import re
class TestUserInput(ut.TestCase):
 def test_request_regex_pattern_non_regex_input(self):
 with mock.patch('builtins.input', return_value='\l\d'):
 with mock.patch('builtins.print', side_effect=[None, Exception('To Break the Loop!')]) as mocked_print:
 with self.assertRaises(Exception):
 ui.request_regex_pattern('')
 mocked_print.assert_called_with('The input was not valid regular expression')

Improving Readability

This was kept with the "patch through context-manager"-syntax for consistency with the previously displayed unit-test.

As you can see, that code is not nice to read due to all the context-managers. As such, it is better to use the patch-decorator instead, as MrBreanBremen did! This will then pass the MagicMock-objects for these functions as parameters to your test, in the order that the patches are applied. Here mocked_input is the MagicMock object of the patched input() method and mocked_print is the MagicMock object of the patched print() method.

import unittest as ut
from unittest import mock
import re
class TestUserInput(ut.TestCase):
 @mock.patch('builtins.input', return_value='\l\d')
 @mock.patch('builtins.print', side_effect=[None, Exception('To Break the Loop!')])
 def test_request_regex_pattern_non_regex_input(self, mocked_input, mocked_print):
 with self.assertRaises(Exception):
 request_regex_pattern('')
 mocked_print.assert_called_with('The input was not valid regular expression')
answered May 21, 2020 at 18:29
Sign up to request clarification or add additional context in comments.

Comments

0

A reliable way to prevent the user from entering the same input more than once would be to simply use a python built-in list. You can use a pre-determined size and just store that many elements inside it. You can append elements to the list and then if the predetermined size is exceeded pop elements from the front (oldest elements). This way you would be able to make sure that the user wouldn't be able to enter the same input as the last N (size of the list) inputs.

answered May 21, 2020 at 16:23

1 Comment

Heya, thanks for the answer! However, I'm not quite looking for a way to prevent the user repeating the same input as before, I'm more looking for a way to check in a unittest that the function request_regex_pattern() repeated that request (aka the while loop) at all.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.