Files
luoluo/tests/test_input_simulator.py

357 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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)