繁体   English   中英

Python使用mock进行多用户输入

[英]Python using mock for a multiple user inputs

这个问题的后续内容。

我在for循环中接受用户输入并编写了一个测试用例test_apple_record 在这个for循环中,它查询一个方法self.dispatch_requested() (未显示),它可以随机返回True或False。 根据这个答案,代码会要求用户提供另一个输入 - 应该调度托盘。

我现在用的是side_effect为了论证mock.patch 如何使用mock自动传递酒店编号作为用户输入? 我仍然希望继续将数字[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]

每次调用输入方法时,它都将返回下一个项目。 因此,每次在循环中,您都会调用两次输入。

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

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

另外,最后,我不确定你的代码实际上是什么样的,但是,根据你在同一个类下的实际实现和测试代码的方式,我强烈建议不要这样做。 尝试类似这样的结构:

创建一个单独的测试类:

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

如果您正在使用遗留代码,那么这个approch并不是真正有用,但是如果您正在测试您正在开发的内容,最好将其转换为更易测试的代码:使您的代码更易于测试,几乎每次都能改进设计。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM