Python 对多个用户输入使用 mock

Python using mock for a multiple user inputs

.

的后续

我在 for 循环中接受用户输入并编写了一个测试用例,test_apple_record。在这个 for 循环中,它查询一个方法 self.dispatch_requested()(未显示),该方法可以随机 return True 或 False。基于这个答案,代码会要求用户输入另一个信息——托盘应该发送到哪里。

我正在为 mock.patch 使用 side_effect 参数。如何使用模拟自动传递酒店号码作为用户输入?我仍然想继续将数字 [5, 6, 7] 传递给 for 循环,但现在还想根据 self.dispatch_requested()

的响应传递酒店编号

谢谢

class SomeClass(unittest.TestCase):
    def apple_counter(self):
        apple_record = {}

        for i in range(3):
            apple_tray = input("enter tray number:")
            apple_record[apple_tray]  =  (i+1)*10
            print("i=%d, apple_record=%s"%(i, apple_record))

            if self.dispath_requested():
                number = input("Enter Hotel number to dispatch this tray:")
                update_hotel_record(number, apple_tray)

    def update_hotel_record(self, number, tray):
        self.hotel_record[number] = tray

    def test_apple_record(self):
        with mock.patch('builtins.input', side_effect=[5, 6, 7]):
            self.apple_counter()

原来也不是没用!由于除了阅读提示外无法知道您需要哪个输入,因此您可以简单地将 input() 函数替换为根据提示给出不同答案的函数。

# first we need a generator for each type of response to `input()`

def tray_number_generator():
    trays = ["1", "5", "7"]
    for i in trays:
        yield i

trays = tray_number_generator()

def room_number_generator():
    rooms = ["112", "543", "724"]
    for i in rooms:
        yield i

rooms = room_number_generator()

# this can be written simpler as a generator expression like this:

trays = (tray for tray in ["1", "5", "7"])
rooms = (room for room in ["112", "543", "724"])

# now you can write a function that selects the next output depending on the prompt:

def mock_input(prompt):
    if "room" in prompt.lower():
        return next(rooms)
    if "tray" in prompt.lower():
        return next(trays)

# this can now be used to replace the `input()` function

with mock.patch('builtins.input', mock_input):
    do_stuff()

您实际上希望 side_effect 看起来像这样:

m_input.side_effect = [1, 100, 2, 200, 3, 300]

每次调用输入法,都会return下一个项目。所以每次在你的循环中,你都会调用 input 两次。

另外,我不知道你的单元测试的最终结构,但是,看到你在循环中调用的第二个输入周围有一个条件语句,你应该围绕该方法设置一个模拟总是 return 正确。

当你到达你想要测试你的代码的场景时 self.dispath_requested() returns false,你必须记住第二个输入不会被调用,所以您的 side_effect 必须相应地重写以匹配您的代码的预期行为。

此外,最后,再次重申,我不确定您的代码实际是什么样子,但是,基于您的实际实现和测试代码似乎在同一 class 下,我强烈建议不这样做。尝试类似这样的结构:

创建一个单独的测试class:

class Tests(unittest.TestCase):
    def setUp(self):
        self.s = SomeClass()

    @patch('__builtin__.input')
    def test_apple_record(self, m_input):
        m_input.side_effect = [1, 100, 2, 200, 3, 300]
        self.s.apple_counter()


if __name__ == '__main__':
    unittest.main()

因此,您创建了 SomeClass 的一个实例,然后这实际上会让您更容易地模拟对象的属性,这将使您的单元测试更容易编写。

您还会注意到我使用了装饰器 (@patch) 而不是 "with" 上下文。这是个人喜好,我发现使用装饰器更容易阅读代码。

希望这对您有所帮助。

我不想深入研究如何模拟 inputdispatch_requested 并将答案结合起来以获得完整的控制并为此方法编写良好的单元测试。我认为更有趣的是如何更改您的设计以使测试(以及代码)更简单和更清晰:

class SomeClass(object):
    def apple_counter(self):
        apple_record = {}

        for i in range(3):
            apple_tray = input("enter tray number:")
            apple_record[apple_tray]  =  (i+1)*10
            print("i=%d, apple_record=%s"%(i, apple_record))
            self._dispatch_and_ask_number()

    def _dispatch_and_ask_number(self):
        if self.dispatch_requested():
            number = self._ask_hotel_number()
            update_hotel_record(number, apple_tray)

    def _ask_try_number(self):
        return input("enter tray number:")

    def _ask_hotel_number(self):
        return input("Enter Hotel number to dispatch this tray:")

    def update_hotel_record(self, number, tray):
        self.hotel_record[number] = tray

现在您可以更好地创建一个新的 class,只需负责询问用户输入,然后模拟它以在您的测试中拥有完整的控制权:

class AskUserInput(class):
    try_number_message = "Enter tray number:"
    hotel_number_message = "Enter Hotel number to dispatch this tray:"

    def try_number(self):
        return input(self.try_number_message)

    def hotel_number(self):
        return input(self.hotel_number_message)

SomeClass可以这样改:

class SomeClass(object):

    _ask = AskUserInput()

    def apple_counter(self):
        apple_record = {}

        for i in range(3):
            apple_tray = self._ask.try_number()
            apple_record[apple_tray]  =  (i+1)*10
            print("i=%d, apple_record=%s"%(i, apple_record))
            self._dispatch_and_ask_number()

    def _dispatch_and_ask_number(self):
        if self.dispatch_requested():
            number = self._ask.hotel_number()
            update_hotel_record(number, apple_tray)

    def update_hotel_record(self, number, tray):
        self.hotel_record[number] = tray

最后是测试

class TestSomeClass(unittest.TestCase):
    @patch("AskUserInput.try_number")
    @patch("AskUserInput.hotel_number")
    def test_apple_record(self, mock_try_number, mock_hotel_number):
        # Now you can use both side_effects and return_value
        # to make your test clear and simple on what you test.

如果你正在玩遗留代码,这个方法并不是很有用,但如果你正在测试你现在正在开发的东西,最好把它变成一个更可测试的代码:让你的代码更可测试几乎每一次改进设计次。