Python单元测试如何正确设置全局变量

8

我有一个简单的方法,根据方法参数将全局变量设置为True或False。

这个全局变量叫做feedback,默认值是False

当我调用setFeedback('y')时,全局变量将被改为feedback = True。 当我调用setFeedback('n')时,全局变量将被改为feedback = False

现在我正在尝试使用Python中的unittest进行测试:

class TestMain(unittest.TestCase):

    def test_setFeedback(self):

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

当我运行这个测试时,我会得到以下错误:AssertionError: False is not true
因为我知道这个方法是正确的,所以我假设全局变量被以某种方式重置了。然而,由于我还是Python环境中的新手,我不确定自己做错了什么。
我已经在这里阅读了一篇关于mocking的文章,但由于我的方法改变了一个全局变量,我不知道mocking是否能解决这个问题。
我将感激任何建议。
这是代码:
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()

1
你能展示一下 setFeedback 函数,包括全局变量和测试中使用的导入吗? - MrBean Bremen
@MrBeanBremen 我添加了代码。这有帮助吗? - CWhite
1
是的,肯定有帮助 - 我在答案中添加了解释。 - MrBean Bremen
2
可以说,在setFeedback中调用inputclearConsole并不合适;它们是在setFeedback返回后,setFeedback调用者可能想要执行的操作。 - chepner
2个回答

10
你的测试有两个问题。
首先,在你的反馈函数中使用了输入(input),这将使测试停止,直到你输入一个键。你可能应该模拟输入(mock input)。同时,你可能需要考虑将对输入(input)的调用移出setFeedback函数(参见@chepner的评论)。
其次,从主文件导入(from main import *)在这里不起作用(除了是不好的风格之外),因为这样会在测试模块中创建全局变量的副本——对变量本身的更改不会传播到副本中。相反,你应该导入模块,以便访问模块中的变量。
第三个问题(这来自于@chepner的答案,我之前忽略了它),你必须确保在测试开始时变量处于已知状态。
以下是应该起作用的代码:
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)

@chepner和MrBeanBremen,首先非常感谢你们的回答。我明天会仔细阅读并调整我的代码。感谢你们的努力。 - CWhite
谢谢你们两个。我还有一个关于模拟的问题: @mock.patch('main.input') 和使用 with mock.patch(main.input) 有什么区别? 提前感谢。 - CWhite
1
没有实际区别。范围可能不同,因为在装饰器版本中,修补始终持续到装饰函数的结束,而在上下文管理器版本中,您可以跳过“as”部分,而在装饰器版本中,您总是必须添加用于模拟对象的参数。除此之外,据我所知,它们是等效的。 - MrBean Bremen

4
你不需要模拟任何东西;你只需要确保每次运行测试前全局变量处于已知状态。此外,使用 from main import * 会在测试模块中创建一个名为 feedback 的新的全局变量,与 setFeedback 修改的 main.feedback 不同。
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)

1
为什么他不需要模拟“input”?我不明白这如何与“setFeedback”中调用的“input”配合使用,但我可能漏掉了什么... - MrBean Bremen
1
哎呀,我没有仔细阅读问题。我以为他在问在调用函数之前如何模拟全局变量。 - chepner
1
好的 - 我现在从你的答案中借用了 setUp,因为我在我的答案中忘记了它... - MrBean Bremen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接