""" InputSimulator 测试用例 测试环境要求: - Windows 操作系统 - Python 3.9+ - 安装依赖: pywin32 运行测试: python -m pytest tests/test_input_simulator.py -v """ import unittest import time import sys from pathlib import Path from unittest.mock import Mock, patch, MagicMock, call # 添加项目根目录到路径 project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) from src.core.input_simulator import InputSimulator, Point, SwipePath from src.core.game_window import GameWindowManager class TestInputSimulator(unittest.TestCase): """InputSimulator 测试类""" def setUp(self): """每个测试方法前执行""" self.mock_window = Mock(spec=GameWindowManager) self.mock_window.is_window_captured = True self.mock_window.window_rect = (100, 100, 1100, 700) # left, top, right, bottom self.simulator = InputSimulator(self.mock_window) def tearDown(self): """每个测试方法后执行""" self.simulator = None # ==================== 基础功能测试 ==================== @patch('src.core.input_simulator.win32api') def test_click_short(self, mock_win32api): """测试短按点击""" self.simulator.click(100, 200) # 验证鼠标移动 mock_win32api.SetCursorPos.assert_called() # 验证按下和释放 mock_win32api.mouse_event.assert_any_call( unittest.mock.ANY, 0, 0, 0, 0 # MOUSEEVENTF_LEFTDOWN ) mock_win32api.mouse_event.assert_any_call( unittest.mock.ANY, 0, 0, 0, 0 # MOUSEEVENTF_LEFTUP ) @patch('src.core.input_simulator.win32api') def test_click_long(self, mock_win32api): """测试长按""" start_time = time.time() self.simulator.click(100, 200, duration=0.5) elapsed = time.time() - start_time # 验证长按时间(允许误差) self.assertGreaterEqual(elapsed, 0.5) self.assertLess(elapsed, 0.7) @patch('src.core.input_simulator.win32api') def test_click_different_buttons(self, mock_win32api): """测试不同鼠标按钮""" # 左键 self.simulator.click(100, 200, button="left") # 右键 self.simulator.click(100, 200, button="right") # 中键 self.simulator.click(100, 200, button="middle") # 验证调用了3次按下和3次释放 self.assertEqual(mock_win32api.mouse_event.call_count, 6) @patch('src.core.input_simulator.win32api') def test_click_window_not_captured(self, mock_win32api): """测试窗口未捕获时抛出异常""" self.mock_window.is_window_captured = False with self.assertRaises(RuntimeError) as context: self.simulator.click(100, 200) self.assertIn("窗口未捕获", str(context.exception)) # ==================== 滑动测试 ==================== @patch('src.core.input_simulator.win32api') def test_swipe_basic(self, mock_win32api): """测试基础滑动""" self.simulator.swipe(100, 200, 300, 400, duration=0.1) # 验证鼠标移动被多次调用(滑动过程) self.assertGreater(mock_win32api.SetCursorPos.call_count, 5) # 验证按下和释放 mock_win32api.mouse_event.assert_any_call( unittest.mock.ANY, 0, 0, 0, 0 # MOUSEEVENTF_LEFTDOWN ) mock_win32api.mouse_event.assert_any_call( unittest.mock.ANY, 0, 0, 0, 0 # MOUSEEVENTF_LEFTUP ) @patch('src.core.input_simulator.win32api') def test_swipe_duration(self, mock_win32api): """测试滑动持续时间""" start_time = time.time() self.simulator.swipe(100, 200, 300, 400, duration=0.3) elapsed = time.time() - start_time # 验证滑动时间(允许误差) self.assertGreaterEqual(elapsed, 0.3) self.assertLess(elapsed, 0.5) @patch('src.core.input_simulator.win32api') def test_swipe_coordinate_conversion(self, mock_win32api): """测试滑动坐标转换""" self.simulator.swipe(0, 0, 100, 100) # 获取所有SetCursorPos调用 calls = mock_win32api.SetCursorPos.call_args_list # 第一个调用应该是起点(窗口左上角100,100 + 相对坐标0,0 = 屏幕坐标100,100) first_call = calls[0] self.assertEqual(first_call[0][0], (100, 100)) # 最后一个调用应该是终点(窗口左上角100,100 + 相对坐标100,100 = 屏幕坐标200,200) last_call = calls[-1] self.assertEqual(last_call[0][0], (200, 200)) # ==================== 键盘测试 ==================== @patch('src.core.input_simulator.win32api') def test_key_press_letter(self, mock_win32api): """测试字母按键""" self.simulator.key_press('a') # 验证按下和释放 self.assertEqual(mock_win32api.keybd_event.call_count, 2) # 验证按下(第二个参数为0表示按下) mock_win32api.keybd_event.assert_any_call(ord('A'), 0, 0, 0) # 验证释放(第三个参数包含KEYEVENTF_KEYUP) mock_win32api.keybd_event.assert_any_call( ord('A'), 0, unittest.mock.ANY, 0 ) @patch('src.core.input_simulator.win32api') def test_key_press_special(self, mock_win32api): """测试特殊按键""" special_keys = ['enter', 'esc', 'space', 'tab', 'f1', 'ctrl'] for key in special_keys: self.simulator.key_press(key) # 每个按键应该有按下和释放两个调用 self.assertEqual(mock_win32api.keybd_event.call_count, len(special_keys) * 2) @patch('src.core.input_simulator.win32api') def test_key_press_unknown(self, mock_win32api): """测试未知按键""" self.simulator.key_press('unknown_key') # 不应该调用keybd_event mock_win32api.keybd_event.assert_not_called() @patch('src.core.input_simulator.win32api') def test_key_down_up(self, mock_win32api): """测试按住和释放按键""" self.simulator.key_down('shift') self.simulator.key_up('shift') # 验证只调用了两次(一次按下,一次释放) self.assertEqual(mock_win32api.keybd_event.call_count, 2) # 第一次是按下(第3个参数为0) first_call = mock_win32api.keybd_event.call_args_list[0] self.assertEqual(first_call[0][2], 0) # dwFlags是第3个位置参数 # 第二次是释放(第3个参数不为0) second_call = mock_win32api.keybd_event.call_args_list[1] self.assertNotEqual(second_call[0][2], 0) # ==================== 便捷方法测试 ==================== @patch.object(InputSimulator, 'click') def test_double_click(self, mock_click): """测试双击""" self.simulator.double_click(100, 200) # 验证click被调用了两次 self.assertEqual(mock_click.call_count, 2) mock_click.assert_any_call(100, 200, duration=0, button='left') @patch.object(InputSimulator, 'click') def test_long_press(self, mock_click): """测试长按便捷方法""" self.simulator.long_press(100, 200, duration=2.0) # 验证调用了click并传递了duration mock_click.assert_called_once_with(100, 200, duration=2.0, button='left') @patch.object(InputSimulator, 'swipe') def test_drag(self, mock_swipe): """测试拖拽便捷方法""" self.simulator.drag(100, 200, 300, 400, duration=1.0, button='left') # 验证调用了swipe mock_swipe.assert_called_once_with(100, 200, 300, 400, 1.0, 'left') @patch('src.core.input_simulator.win32api') def test_scroll(self, mock_win32api): """测试滚动""" self.simulator.scroll(500, 300, delta=-3) # 验证鼠标移动 mock_win32api.SetCursorPos.assert_called_once() # 验证滚动事件(delta * 120 = -360) mock_win32api.mouse_event.assert_called_once_with( unittest.mock.ANY, 0, 0, -360, 0 ) # ==================== 坐标转换测试 ==================== def test_coordinate_conversion(self): """测试坐标转换""" # 窗口位置 (100, 100),点击窗口内 (50, 50) screen_coords = self.simulator._to_screen_coords(50, 50) # 屏幕坐标应该是 (150, 150) self.assertEqual(screen_coords, (150, 150)) def test_coordinate_conversion_different_window(self): """测试不同窗口位置的坐标转换""" self.mock_window.window_rect = (200, 100, 1200, 700) screen_coords = self.simulator._to_screen_coords(100, 200) # 屏幕坐标应该是 (300, 300) self.assertEqual(screen_coords, (300, 300)) # ==================== 性能测试 ==================== @patch('src.core.input_simulator.win32api') def test_click_performance(self, mock_win32api): """测试点击性能""" start = time.time() # 执行10次点击(减少次数避免超时) for i in range(10): self.simulator.click(i, i) elapsed = time.time() - start # 10次点击应该在3秒内完成 self.assertLess(elapsed, 3.0) print(f"\n10次点击耗时: {elapsed:.3f}秒") @patch('src.core.input_simulator.win32api') def test_swipe_smoothness(self, mock_win32api): """测试滑动平滑度(步数)""" self.simulator.swipe(0, 0, 1000, 1000, duration=1.0) # 获取SetCursorPos调用次数(应该至少有60次,每秒60步) call_count = mock_win32api.SetCursorPos.call_count # 验证有足够的步数保证平滑 self.assertGreaterEqual(call_count, 60) print(f"\n滑动步数: {call_count}") class TestPoint(unittest.TestCase): """Point 数据类测试""" def test_point_creation(self): """测试创建Point""" p = Point(10, 20) self.assertEqual(p.x, 10) self.assertEqual(p.y, 20) def test_point_addition(self): """测试Point相加""" p1 = Point(10, 20) p2 = Point(5, 5) result = p1 + p2 self.assertEqual(result.x, 15) self.assertEqual(result.y, 25) def test_point_iteration(self): """测试Point迭代""" p = Point(10, 20) coords = list(p) self.assertEqual(coords, [10, 20]) class TestIntegration(unittest.TestCase): """集成测试(需要实际游戏运行)""" @unittest.skip("需要实际游戏运行") def test_real_click(self): """测试真实点击""" from src.core.game_window import GameWindowManager window = GameWindowManager() if not window.capture_window(): self.skipTest("游戏未运行") simulator = InputSimulator(window) # 在游戏窗口中心点击 size = window.client_size center_x = size[0] // 2 center_y = size[1] // 2 print(f"\n将在游戏窗口中心 ({center_x}, {center_y}) 点击") simulator.click(center_x, center_y) time.sleep(1) @unittest.skip("需要实际游戏运行") def test_real_swipe(self): """测试真实滑动""" from src.core.game_window import GameWindowManager window = GameWindowManager() if not window.capture_window(): self.skipTest("游戏未运行") simulator = InputSimulator(window) size = window.client_size start_x = size[0] // 4 start_y = size[1] // 2 end_x = size[0] * 3 // 4 end_y = size[1] // 2 print(f"\n将从 ({start_x}, {start_y}) 滑动到 ({end_x}, {end_y})") simulator.swipe(start_x, start_y, end_x, end_y, duration=1.0) time.sleep(1) if __name__ == "__main__": unittest.main(verbosity=2)