如何为含用户输入的Python函数编写有效单元测试?测试未覆盖问题求解
问题:Driver函数测试未被覆盖率统计纳入及修正方法
被测Driver函数
def driver(): n = int(input("Enter number of rows: ")) m = int(input("Enter number of columns: ")) if n == 0 or m == 0: raise ValueError("Invalid grid size.") grid = [] for row in range(n): row_i = input("Enter the space-separated cells of row " + str(row + 1) + ": ") row_i = list(map(lambda x: int(x), row_i.split())) if len(row_i) != m: raise IndexError("Invalid input for the given number of columns.") if any([cell not in [0, 1] for cell in row_i]): raise ValueError("Invalid value of cell, a cell can only have 0 or 1 as a value.") grid.append(row_i) print("Initial grid: ") print_grid(grid)
当前测试代码
class TestGameOfLife(unittest.TestCase): def setUp(self): self.driver = game_of_life.driver @mock.patch('game_of_life.input', create=True) def test_driver_invalid_num_rows(self, mocked_input): mocked_input.side_effect = ["0", "5"] self.assertRaisesRegex(ValueError, "Invalid grid size.", self.driver)
核心问题
当前测试及被测代码均未被覆盖率统计工具纳入,推测测试方式或覆盖率配置存在问题。
修正方案
1. 确保覆盖率工具正确配置
使用coverage.py时,需明确指定要覆盖的源模块,避免遗漏。示例命令:
# 运行测试并统计指定模块的覆盖率 coverage run --source=game_of_life -m unittest test_game_of_life.py # 生成覆盖率报告 coverage report # 生成可视化HTML报告(可选) coverage html
其中--source=game_of_life指定仅统计该模块的代码覆盖率,确保被测代码被纳入统计范围。
2. 优化测试代码逻辑
问题点排查
原测试中,assertRaisesRegex直接传递self.driver(函数对象)的方式,部分覆盖率工具可能无法正确追踪函数执行路径;同时未处理driver依赖的print_grid函数,可能导致测试额外输出或逻辑干扰。
修正后的测试代码
import unittest from unittest import mock import game_of_life class TestGameOfLife(unittest.TestCase): def setUp(self): self.driver = game_of_life.driver @mock.patch('game_of_life.input') def test_driver_invalid_num_rows(self, mocked_input): mocked_input.side_effect = ["0", "5"] # 显式调用被测函数,确保覆盖率工具能追踪执行路径 with self.assertRaisesRegex(ValueError, "Invalid grid size."): self.driver() @mock.patch('game_of_life.input') def test_driver_invalid_column_count(self, mocked_input): mocked_input.side_effect = ["2", "2", "1", "0 1"] with self.assertRaisesRegex(IndexError, "Invalid input for the given number of columns."): self.driver() @mock.patch('game_of_life.input') def test_driver_invalid_cell_value(self, mocked_input): mocked_input.side_effect = ["1", "2", "1 2"] with self.assertRaisesRegex(ValueError, "Invalid value of cell, a cell can only have 0 or 1 as a value."): self.driver() @mock.patch('game_of_life.input') def test_driver_valid_grid(self, mocked_input): mocked_input.side_effect = ["2", "2", "1 0", "0 1"] # Mock掉print_grid,避免测试输出并聚焦核心逻辑 with mock.patch('game_of_life.print_grid') as mock_print: self.driver() # 断言input调用次数符合预期 self.assertEqual(mocked_input.call_count, 4) # 断言print_grid被正确调用 mock_print.assert_called_once_with([[1,0], [0,1]])
关键说明
- 显式调用被测函数:将
assertRaisesRegex的函数对象参数改为显式调用self.driver(),确保覆盖率工具能正确捕捉函数执行轨迹。 - Mock依赖函数:对
print_grid这类非核心逻辑函数进行mock,避免测试产生无关输出,同时可通过断言验证函数调用正确性。 - 覆盖所有分支:补充多场景测试,覆盖无效列数、无效单元格值、合法网格等分支,确保测试覆盖完整,覆盖率统计准确。
内容的提问来源于stack exchange,提问作者Sejal Gupta
相关产品推荐
相关产品推荐

