测试给定的输入值是不是不会从 while True 循环中中断

Posted

技术标签:

【中文标题】测试给定的输入值是不是不会从 while True 循环中中断【英文标题】:Test if given input value does not break from while True loop测试给定的输入值是否不会从 while True 循环中中断 【发布时间】:2021-11-28 12:36:19 【问题描述】:

我想对一个名为 prompt_process 的函数进行单元测试。该函数的作用是根据input的返回值提示是调用另一个函数process,还是干脆退出程序:

process.py

import sys


def process():
    pass


def prompt_process():
    answer = input("Do you want to process? (y/n)").lower()
    while True:
        if answer == 'y':
            return process()
        elif answer == 'n':
            sys.exit(0)
        else:
            answer = input("Invalid answer - please type y or n").lower()

使用模拟测试if/elif 子句非常简单:

test_process.py

import unittest
from unittest import mock

import process


class TestProcess(unittest.TestCase):
    @mock.patch('process.process')
    def test_prompt_process_calls_process_if_input_is_y(self, mock_process):
        with mock.patch('process.input', return_value='y'):
            process.prompt_process()
        mock_process.assert_called_once()
        
    def test_prompt_process_exits_if_input_is_n(self):
        with mock.patch('process.input', return_value='n'):
            with self.assertRaises(SystemExit):
                process.prompt_process()


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

但我不知道如何测试函数的else 子句。理想情况下,它应该测试循环是否针对yn 以外的输入值继续运行,但由于我无法“模拟”while True 循环,因此我不确定如何继续。

有什么解决方案可以对此进行测试,还是我一开始就不应该为此烦恼?

【问题讨论】:

【参考方案1】:

在函数中调用sys.exit(0) 不能被视为最佳实践,可能存在打开的文件、待处理的事务或其他事务性/暂时的待处理操作。

建议是创建一个执行退出的函数,并在要测试的函数中调用该函数。这样你的函数应该很容易测试。

import sys


def process():
    pass

def leave():
    sys.exit(0)

def prompt_process():
    answer = input("Do you want to process? (y/n)").lower()
    while True:
        if answer == 'y':
            return process()
        elif answer == 'n':
            return leave()
        else:
            answer = input("Invalid answer - please type y or n").lower()

【讨论】:

【参考方案2】:

无创方式

一种非侵入性方法是通过使用side_effect 而不是return_value 引发异常来检查input 是否被调用了两次。

这对实现做了一个小假设。

# Define a custom exception to ensure it's not raised elsewhere
class EndError(Exception):
    pass
def test_prompt_process_does_not_break_from_while_true_loop_if_input_is_invalid(self):
    with mock.patch('process.input', side_effect=('invalid', EndError)):
        with self.assertRaises(EndError):
            process.prompt_process()

微创方式(无操作功能)

另一种微创方式是在while 循环中定义并调用noop 函数。

def noop():
    pass
while True:
    noop()  # Add this
    # ...
@mock.patch('process.noop', side_effect=(None, EndError))
def test_prompt_process_does_not_break_from_while_true_loop_if_input_is_invalid(self, mock_noop):
    with mock.patch('process.input', return_value='invalid'):
        with self.assertRaises(EndError):
            process.prompt_process()
    self.assertEqual(mock_noop.call_count, 2)

测试最大迭代次数

如果您的实现限制了尝试次数,那么您不必处理异常。

max_tries = 10
for i in range(max_tries):
    noop()
    # ...
    if i < max_tries - 1:
        answer = input("Invalid answer - please type y or n").lower()
    else:
        _ = input("Invalid answer - press enter to end")  # Either
        # print("Invalid answer")                         # or
@mock.patch('process.noop')
def test_prompt_process_accepts_10_tries_if_input_is_invalid(self, mock_noop):
    with mock.patch('process.input', return_value='invalid') as mock_input:
        process.prompt_process()
    self.assertEqual(mock_input.call_count, 11)  # This requires knowledge about the implementation
    self.assertEqual(mock_noop.call_count, 10)   # This only makes an assumption that noop is called

【讨论】:

谢谢,这很好用。我很高兴找到不同的方法来回答这个问题!

以上是关于测试给定的输入值是不是不会从 while True 循环中中断的主要内容,如果未能解决你的问题,请参考以下文章

循环语句

Shell while循环

shell的while循环

在while循环中将布尔值更改为true? C++

Linux Gvim shell while循环

java nio 方面的,我不明白while(true)表示啥?