修改游戏窗口获取逻辑以及完善获取模拟点击相关逻辑

This commit is contained in:
moweishan
2026-03-20 12:11:27 +08:00
parent b67c5be2f3
commit 62f717dde4
8 changed files with 2221 additions and 31 deletions

View File

@@ -0,0 +1,356 @@
"""
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)