【问题标题】:Python3: Testing repeated user inputPython3:测试重复的用户输入
【发布时间】:2020-05-21 16:09:34
【问题描述】:

我在单元测试方面有点初学者,但仍在尝试熟悉其中的一些内容。这是我自己项目的一部分,我一直坚持如何全面测试它。

在这个方向上已经有一个问题,但它只关心如何反复询问用户输入,而不是如何对它进行单元测试。

目标:

我有一个要求用户输入并在输入无效时重复请求的功能。我的目标是弄清楚如果用户提供无效输入,如何测试输入请求是否重复。我的意思是,我正在尝试测试在定义的情况下重复输入请求的机制是否按预期工作。

守则:

该函数要求用户提供一个正则表达式,并将其编译为来自重新打包(python 标准库)的 SRE_Pattern 对象。如果用户提供了一个输入并且它不是一个有效的表达式,那么输入请求会被重复。

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

到目前为止的测试:

到目前为止,我可以测试的是,对于有效输入(例如\d\d),我是否使用模拟得到正确的输出(该正则表达式的SRE_Patternobject)。

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))

我已经考虑过这个问题并在谷歌上搜索了一段时间,但无法得到令人满意的答案。

有没有一种明智的方法来测试输入请求是否被重复? 那里有哪些最佳实践?

对于解决方案,使用 python 的默认 unittest 库不是强制性的。但是,通常会首选使用标准库的解决方案,因为这样可以减少我正在处理的项目所需的需求数量。

非常感谢您的宝贵时间!

【问题讨论】:

  • 我最近回复了somewhat similar question - 请检查这是否对您有帮助。
  • @MrBeanBremen Heyya,谢谢你的提示!这些问题看起来确实非常相似,我认为您在那里推荐的内容很可能也适用于此,但我不太了解用于测试的代码,无论是问题还是答案。我知道,与我的第一次测试类似,您修补了内置输入,但是在问答中用于修补的语法让我很困惑。
  • @MrBeanBremen 我想我在那里掌握了一些你的答案,是不是通过修补通用打印函数以在执行实际操作时抛出异常,它会触发“除外”部分main() 中第一个 while 循环的 try-except 语句,触发仅在此处发生的字符串的打印,然后使用 mock_print.assert_call_with() 您可以断言任何打印调用的存在(因为通常打印已修补)与仅出现在该特定“catch”位中的字符串('您需要输入一个数字!')?
  • 是的,你没看错。如果需要,我可以整理一个更具体的答案,但看起来你已经得到了这个:)
  • @MrBeanBremen 那么非常感谢您的提示!我想我会根据您在那里建议的回复写出适合此代码的答案。不过,补丁中的语法差异让我很好奇(特别是考虑到最佳实践/最佳可读性的问题)。你愿意评论一下在哪种情况下人们会更喜欢其中一种吗?

标签: python python-3.x unit-testing input


【解决方案1】:

BreanBremen 先生为我指出了正确的方向,将我与他的答案联系起来,并帮助我解决了一些关于语法和理解原理的后续问题。为了更容易理解,我想详细说明和简化他在那里使用的原理。

原理

检查这一点的方法是修补在您要检查的情况下调用的函数。这会将它们替换为 MagicMock 对象,这些对象知道它们何时被调用以及使用哪些参数!

一旦函数被修补,您就可以使用 MagicMocks 断言方法,例如 assert_called_with()assert_called_once()assert_called() 和/或 assert_not_called() 来检查是否调用了这个 MagicMock 对象替换的函数并使用您期望的参数。

现在这如何适用于这个问题?

首先回到我们的问题。我们有 3 个测试用例:

1) 用户提供可以编译成 SRE_Pattern 对象的有效正则表达式 --> 返回一个 SRE_Pattern 对象

2) 用户不提供任何输入(只需按回车键)--> 返回无

3) 用户提供的输入无法编译成 SRE_Pattern 对象(无效输入),触发 print("The input was not valid regular expression") 语句 --> Never Returns nothing

我们只关心 3) 的情况,因为案例 1) 和 2) 具有定义的输出,我们可以使用“正常”单元测试轻松检查,而案例 3) 明确不能输出任何内容,导致我们我们的问题。

正如在 3) 中已经暗示的那样,在这些情况下只调用 print 函数。这意味着对于我们的测试,我们应该修补它,以便我们可以接收此函数的 MagicMock 对象并将其assert_called_with() 与 print-statement 获取的字符串一起使用,因为该字符串仅出现在该部分代码中。在这些情况下它是“独一无二的”!

不过,我们还有一个问题要解决。一旦我们像以前一样修补了builtins.input,但是有一些导致我们的while循环重复的东西,我们仍然会被困在一个while循环中!函数调用request_regex_pattern() 永远不会结束!由于我们无法正常退出该函数,因此我们必须通过引发Exception 来退出。因此,我们还需要将其修补到当这些情况发生时调用的函数之一。在这种情况下,我们可以方便地将这个副作用添加到我们的 print 补丁中。然后我们可以使用上下文管理器with self.assertRaises(Exception) 捕获Exception,以防止我们的测试失败。

代码:

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')

提高可读性

为了与之前显示的单元测试保持一致,这与“通过上下文管理器打补丁”的语法保持一致。

如您所见,由于所有的上下文管理器,该代码并不好阅读。因此,最好使用补丁装饰器,就像 MrBreanBremen 所做的那样!然后,这将按照应用补丁的顺序将这些函数的 MagicMock 对象作为参数传递给您的测试。这里mocked_input 是修补后的input() 方法的MagicMock 对象,mocked_print 是修补后的print() 方法的MagicMock 对象。

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')

【讨论】:

    【解决方案2】:

    防止用户多次输入相同输入的可靠方法是简单地使用 python 内置列表。您可以使用预先确定的大小并在其中存储那么多元素。您可以将元素附加到列表中,然后如果超过预定大小,则从前面弹出元素(最旧的元素)。这样您就可以确保用户无法输入与最后 N 个(列表大小)输入相同的输入。

    【讨论】:

    • 嘿,谢谢你的回答!但是,我并不是在寻找一种方法来防止用户重复与以前相同的输入,我更多的是在寻找一种方法来检查单元测试中函数 request_regex_pattern() 重复了该请求(又名 while 循环)完全没有。
    猜你喜欢
    • 2018-01-23
    • 1970-01-01
    • 2014-06-14
    • 2019-10-13
    • 1970-01-01
    • 2017-03-22
    • 2019-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多