简体   繁体   English

Python使用mock进行多用户输入

[英]Python using mock for a multiple user inputs

A follow on for this question . 这个问题的后续内容。

I am accepting user input in a for loop and have written a test case, test_apple_record . 我在for循环中接受用户输入并编写了一个测试用例test_apple_record In this for loop, it queries a method self.dispatch_requested() ( not shown) which can randomly return True or False. 在这个for循环中,它查询一个方法self.dispatch_requested() (未显示),它可以随机返回True或False。 Based on this answer, the code asks user for another input -- where the tray should be dispatched. 根据这个答案,代码会要求用户提供另一个输入 - 应该调度托盘。

I am using the side_effect argument for mock.patch . 我现在用的是side_effect为了论证mock.patch How to automatically pass the hotel number as the user input using mock? 如何使用mock自动传递酒店编号作为用户输入? I still want to continue passing the numbers [5, 6, 7] to the for loop, but now also want to pass in the hotel number based on response by self.dispatch_requested() 我仍然希望继续将数字[5, 6, 7]传递给for循环,但现在还要根据self.dispatch_requested()响应传递酒店编号

thank you 谢谢

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

Turns out my last answer was not useless after all! 结果我的最后答案毕竟没用! As there is no way of knowing which input you require but to read the prompt, you could simply replace the input() function with one that gives different answers depending on the prompt. 由于无法知道您需要哪个输入但是要读取提示,您可以简单地将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()

You actually want your side_effect to look like this: 你真的希望你的side_effect看起来像这样:

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

Each time the input method is called, it will return the next item. 每次调用输入方法时,它都将返回下一个项目。 So each time in your loop, you call input twice. 因此,每次在循环中,您都会调用两次输入。

Also, I don't know the final structure of your unit test, however, seeing that you have a conditional statement around the second input that is called in your loop, you should probably set a mock around that method to always return True. 另外,我不知道你的单元测试的最终结构,但是,看到你在循环中调用的第二个输入周围有一个条件语句,你应该设置一个围绕该方法的模拟以始终返回True。

When you get to the scenario where you want to test your code for when self.dispath_requested() returns false, you have to keep in mind the second input will not be called, so your side_effect has to be re-written accordingly to match the expected behaviour for your code. 当你到达想要测试你的代码的场景时,self.dispath_requested()返回false,你必须记住第二个输入不会被调用,所以你的side_effect必须相应地重写以匹配您的代码的预期行为。

Also, finally, again, I'm not sure what your code actually looks like, however, based on how you seem to have your actual implementation and test code under the same class, I strongly advise not doing that. 另外,最后,我不确定你的代码实际上是什么样的,但是,根据你在同一个类下的实际实现和测试代码的方式,我强烈建议不要这样做。 Try a structure similar to this: 尝试类似这样的结构:

Create a separate test 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()

So, you create an instance of SomeClass, and then this in effect will let you mock out the properties of the object much easier, which will make your unit tests much easier to write. 因此,您创建了一个SomeClass实例,然后实际上这将使您更容易模拟对象的属性,这将使您的单元测试更容易编写。

You will also notice that I used a decorator (@patch) instead of the "with" context. 您还会注意到我使用了装饰器(@patch)而不是“with”上下文。 It's a personal preference, and I find it much easier to read the code using decorators. 这是个人偏好,我发现使用装饰器阅读代码要容易得多。

Hope this helps. 希望这可以帮助。

I don't want go deeply in how mock both input and dispatch_requested and couple the answers to have a complete control and write a good unit test for this method. 我不想深入研究如何模拟inputdispatch_requested并将答案结合起来以获得完整的控制并为此方法编写一个良好的单元测试。 I think it is more interesting how to change your design to make the test (and so the code) simpler and more clear: 我认为如何更改设计以使测试(以及代码)更简单,更清晰更有趣:

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

Now you are in a better position to create a new class with just one responsibility of ask user input and then mock it to have a complete control in your test: 现在,您可以更好地创建一个新类,只需要一个用户输入的责任,然后模拟它以在测试中拥有完整的控件:

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)

And SomeClass can be changed like: 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

And finally the test 最后是测试

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.

If you are playing with legacy code this approch is not really useful, but if you are testing something that you are developing now is better to turn it in a more testable code: make your code more testable improve design almost every times. 如果您正在使用遗留代码,那么这个approch并不是真正有用,但是如果您正在测试您正在开发的内容,最好将其转换为更易测试的代码:使您的代码更易于测试,几乎每次都能改进设计。

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

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