8

I have a simple method that sets a global variable to either True or False depending on the method parameter.

This global variable is called feedback and has a default value of False.

When I call setFeedback('y') the global variable will be changed to be feedback = True. When I call setFeedback('n') the global variable will be changed to be feedback = False.

Now I am trying to test this using unittest in Python:

class TestMain(unittest.TestCase):

    def test_setFeedback(self):

        self.assertFalse(feedback)
        setFeedback('y')
        self.assertTrue(feedback)

When I run this test I get the following error: AssertionError: False is not true.

Since I know that the method works correctly, I assume that the global variables are reset somehow. However, since I am still very new to the Python environment, I don't know exactly what I am doing wrong.

I have already read an article here about mocking, but since my method changes a global variable, I don't know if mocking can solve this.

I would be grateful for suggestions.

Here is the code:

main.py:

#IMPORTS
from colorama import init, Fore, Back, Style
from typing import List, Tuple

#GLOBAL VARIABLE
feedback = False

#SET FEEDBACK METHOD
def setFeedback(feedbackInput):
    """This methods sets the feedback variable according to the given parameter.
       Feedback can be either enabled or disabled.

    Arguments:
        feedbackInput {str} -- The feedback input from the user. Values = {'y', 'n'}
    """

    #* ACCESS TO GLOBAL VARIABLES
    global feedback

    #* SET FEEDBACK VALUE
    # Set global variable according to the input
    if(feedbackInput == 'y'):

        feedback = True
        print("\nFeedback:" + Fore.GREEN + " ENABLED\n" + Style.RESET_ALL)
        input("Press any key to continue...")

        # Clear the console
        clearConsole()

    else:
        print("\nFeedback:" + Fore.GREEN + " DISABLED\n" + Style.RESET_ALL)
        input("Press any key to continue...")

        # Clear the console
        clearConsole()

test_main.py:

import unittest
from main import *

class TestMain(unittest.TestCase):

    def test_setFeedback(self):

        self.assertFalse(feedback)
        setFeedback('y')
        self.assertTrue(feedback)


if __name__ == '__main__':
    unittest.main()
4
  • 1
    Can you show the setFeedback function, including the global variable, and the imports you use in your test? Commented May 1, 2020 at 13:25
  • @MrBeanBremen I added the code. Does that help? Commented May 1, 2020 at 17:46
  • 1
    Yes, definitely helped - I added the explanation in the answer. Commented May 1, 2020 at 18:25
  • 2
    Arguably, the call to input and clearConsole don't belong in setFeedback at all; they are something that the caller of setFeedback might want to do after setFeedback returns. Commented May 1, 2020 at 19:12

2 Answers 2

10

There are two problems with your test.

First, you use input in your feedback function, that will stall the test until you enter a key. You probably should mock input. Also you may consider that the call to input does not belong in setFeedback (see comment by @chepner).

Second, from main import * will not work here (apart from being bad style), because this way you create a copy of your global variable in the test module - changes in the variable itself will not be propagated to the copy. You should instead import the module, so that you will access the variable in the module.

Third (this is taken from the answer by @chepner, I had missed that), you have to make sure that the variable is at a known state at test begin.

Here is what should work:

import unittest
from unittest import mock

import main  # importing the module lets you access the original global variable


class TestMain(unittest.TestCase):

    def setUp(self):
        main.feedback = False  # make sure the state is defined at test start

    @mock.patch('main.input')  # patch input to run the test w/o user interaction
    def test_setFeedback(self, mock_input):
        self.assertFalse(main.feedback)
        main.setFeedback('y')
        self.assertTrue(main.feedback)
Sign up to request clarification or add additional context in comments.

3 Comments

@chepner and MrBeanBremen First of all, thank you very much for the answers. I will read everything carefully tomorrow and adjust my code. Thank you for your efforts.
Thanks to both of you. I have one more question regarding mocking: What is the difference between @mock.patch('main.input') and using with mock.patch(main.input)? Thanks in advance.
No real difference. The scope can be different, because in the decorator version, the patching always lasts to the end of the decorated function, and in the context manager version you can skip the as part, while in the decorator version you always have to add the argument for the mocked object. Apart from that, they are equivalent, as far as I know.
4

You don't need to mock anything; you just need to ensure that the global variable is in a known state before running each test. Also, using from main import * creates a new global named feedback in your test module, distinct from main.feedback which setFeedback is modifying.

import main

class TestMain(unittest.TestCase):

    def setUp(self):
        main.feedback = False

    def test_setFeedback(self):

        self.assertFalse(feedback)
        main.setFeedback('y')
        self.assertTrue(feedback)

3 Comments

Why doesn't he have to mock input? I don't see how that will work with input called in setFeedback, but I may be missing something...
Oops, I didn't read the question closely enough. I though he was asking about mocking the global before calling the function.
Ok - I now stole the setUp from your answer, as I had forgotten that in my answer...

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.