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

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

374
tests/test_game_window.py Normal file
View File

@@ -0,0 +1,374 @@
"""
GameWindowManager 测试用例
测试环境要求:
- Windows 操作系统
- Python 3.9+
- 安装依赖: pywin32, psutil
运行测试:
python -m pytest tests/test_game_window.py -v
python -m pytest tests/test_game_window.py::TestGameWindowManager::test_find_window_by_exe -v
"""
import unittest
import time
import sys
from pathlib import Path
from unittest.mock import Mock, patch, MagicMock
import psutil
# 添加项目根目录到路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root))
from src.core.game_window import GameWindowManager
class TestGameWindowManager(unittest.TestCase):
"""GameWindowManager 测试类"""
def setUp(self):
"""每个测试方法前执行"""
self.manager = GameWindowManager()
def tearDown(self):
"""每个测试方法后执行"""
self.manager = None
# ==================== 基础属性测试 ====================
def test_initial_state(self):
"""测试初始状态"""
self.assertIsNone(self.manager.hwnd)
self.assertIsNone(self.manager.process_id)
self.assertFalse(self.manager.is_window_captured)
self.assertEqual(self.manager.client_size, (0, 0))
self.assertEqual(self.manager.window_rect, (0, 0, 0, 0))
def test_game_constants(self):
"""测试游戏常量配置"""
self.assertEqual(self.manager.GAME_EXE_NAME, "ycgame.exe")
self.assertEqual(self.manager.GAME_WINDOW_TITLE, "桃源深处有人家")
# ==================== 模拟窗口测试 ====================
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.win32process')
@patch('src.core.game_window.psutil')
def test_find_window_by_exe_success(self, mock_psutil, mock_win32process, mock_win32gui):
"""测试通过exe名成功查找窗口"""
# 模拟窗口句柄和进程ID
mock_hwnd = 12345
mock_pid = 67890
# 设置模拟返回值
mock_win32gui.IsWindowVisible.return_value = True
mock_win32gui.GetWindowText.return_value = "桃源深处有人家"
mock_win32gui.GetWindowRect.return_value = (100, 100, 1100, 700)
mock_win32process.GetWindowThreadProcessId.return_value = (None, mock_pid)
# 模拟进程对象
mock_process = Mock()
mock_process.exe.return_value = "C:\\Game\\ycgame.exe"
mock_psutil.Process.return_value = mock_process
# 模拟EnumWindows回调
def mock_enum_windows(callback, extra):
callback(mock_hwnd, extra)
return True
mock_win32gui.EnumWindows = mock_enum_windows
# 执行测试
result = self.manager.find_window_by_exe("ycgame.exe")
# 验证结果
self.assertTrue(result)
self.assertEqual(self.manager.hwnd, mock_hwnd)
self.assertEqual(self.manager.process_id, mock_pid)
self.assertTrue(self.manager.is_window_captured)
self.assertEqual(self.manager.client_size, (1000, 600))
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.win32process')
@patch('src.core.game_window.psutil')
def test_find_window_by_exe_not_found(self, mock_psutil, mock_win32process, mock_win32gui):
"""测试通过exe名未找到窗口"""
# 模拟没有匹配的窗口
def mock_enum_windows(callback, extra):
return True
mock_win32gui.EnumWindows = mock_enum_windows
result = self.manager.find_window_by_exe("nonexistent.exe")
self.assertFalse(result)
self.assertIsNone(self.manager.hwnd)
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.win32process')
def test_find_window_by_title_success(self, mock_win32process, mock_win32gui):
"""测试通过标题成功查找窗口"""
mock_hwnd = 12345
mock_pid = 67890
mock_win32gui.FindWindow.return_value = mock_hwnd
mock_win32gui.GetWindowRect.return_value = (100, 100, 1100, 700)
mock_win32process.GetWindowThreadProcessId.return_value = (None, mock_pid)
result = self.manager.find_window_by_title("桃源深处有人家")
self.assertTrue(result)
self.assertEqual(self.manager.hwnd, mock_hwnd)
self.assertEqual(self.manager.process_id, mock_pid)
@patch('src.core.game_window.win32gui')
def test_find_window_by_title_not_found(self, mock_win32gui):
"""测试通过标题未找到窗口"""
mock_win32gui.FindWindow.return_value = 0
# 模拟EnumWindows也没有找到
def mock_enum_windows(callback, extra):
return True
mock_win32gui.EnumWindows = mock_enum_windows
result = self.manager.find_window_by_title("不存在的窗口")
self.assertFalse(result)
self.assertIsNone(self.manager.hwnd)
# ==================== 综合查找测试 ====================
@patch.object(GameWindowManager, 'find_window_by_exe')
@patch.object(GameWindowManager, 'find_window_by_title')
def test_find_window_use_exe_success(self, mock_find_by_title, mock_find_by_exe):
"""测试综合查找 - 通过exe成功"""
mock_find_by_exe.return_value = True
result = self.manager.find_window(use_exe=True)
self.assertTrue(result)
mock_find_by_exe.assert_called_once()
mock_find_by_title.assert_not_called()
@patch.object(GameWindowManager, 'find_window_by_exe')
@patch.object(GameWindowManager, 'find_window_by_title')
def test_find_window_use_exe_fail_then_title(self, mock_find_by_title, mock_find_by_exe):
"""测试综合查找 - exe失败后用标题"""
mock_find_by_exe.return_value = False
mock_find_by_title.return_value = True
result = self.manager.find_window(use_exe=True)
self.assertTrue(result)
mock_find_by_exe.assert_called_once()
mock_find_by_title.assert_called_once()
@patch.object(GameWindowManager, 'find_window_by_title')
def test_find_window_use_title_only(self, mock_find_by_title):
"""测试综合查找 - 只用标题"""
mock_find_by_title.return_value = True
result = self.manager.find_window(use_exe=False)
self.assertTrue(result)
mock_find_by_title.assert_called_once()
# ==================== 窗口操作测试 ====================
@patch('src.core.game_window.win32gui')
def test_bring_to_front(self, mock_win32gui):
"""测试窗口置前"""
self.manager._hwnd = 12345
mock_win32gui.IsWindow.return_value = True
self.manager.bring_to_front()
mock_win32gui.SetForegroundWindow.assert_called_once_with(12345)
@patch('src.core.game_window.win32gui')
def test_bring_to_front_not_captured(self, mock_win32gui):
"""测试窗口未捕获时不执行置前"""
mock_win32gui.IsWindow.return_value = False
self.manager.bring_to_front()
mock_win32gui.SetForegroundWindow.assert_not_called()
@patch('src.core.game_window.win32gui')
def test_click(self, mock_win32gui):
"""测试点击坐标转换"""
self.manager._hwnd = 12345
mock_win32gui.IsWindow.return_value = True
mock_win32gui.GetWindowRect.return_value = (100, 100, 1100, 700)
# 执行点击
self.manager.click(500, 300)
# 验证坐标转换 (窗口左上角100,100 + 相对坐标500,300 = 屏幕坐标600,400)
# 注意:实际点击功能还未实现,这里只测试坐标转换逻辑
# ==================== 窗口信息测试 ====================
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.psutil')
def test_get_window_info_captured(self, mock_psutil, mock_win32gui):
"""测试获取窗口信息 - 已捕获"""
mock_hwnd = 12345
mock_pid = 67890
self.manager._hwnd = mock_hwnd
self.manager._process_id = mock_pid
mock_win32gui.IsWindow.return_value = True
mock_win32gui.GetWindowText.return_value = "桃源深处有人家"
mock_win32gui.GetWindowRect.return_value = (100, 100, 1100, 700)
mock_process = Mock()
mock_process.exe.return_value = "C:\\Game\\ycgame.exe"
mock_psutil.Process.return_value = mock_process
info = self.manager.get_window_info()
self.assertTrue(info["captured"])
self.assertEqual(info["hwnd"], mock_hwnd)
self.assertEqual(info["title"], "桃源深处有人家")
self.assertEqual(info["process_id"], mock_pid)
self.assertEqual(info["exe_path"], "C:\\Game\\ycgame.exe")
self.assertEqual(info["rect"], (100, 100, 1100, 700))
self.assertEqual(info["size"], (1000, 600))
def test_get_window_info_not_captured(self):
"""测试获取窗口信息 - 未捕获"""
info = self.manager.get_window_info()
self.assertFalse(info["captured"])
self.assertIsNone(info["hwnd"])
self.assertIsNone(info["title"])
self.assertIsNone(info["process_id"])
self.assertIsNone(info["exe_path"])
self.assertIsNone(info["rect"])
self.assertIsNone(info["size"])
# ==================== 错误处理测试 ====================
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.win32process')
@patch('src.core.game_window.psutil')
def test_find_window_by_exe_access_denied(self, mock_psutil, mock_win32process, mock_win32gui):
"""测试访问被拒绝时的处理"""
mock_win32gui.IsWindowVisible.return_value = True
mock_win32process.GetWindowThreadProcessId.return_value = (None, 12345)
mock_psutil.Process.side_effect = psutil.AccessDenied("Access denied")
def mock_enum_windows(callback, extra):
callback(12345, extra)
return True
mock_win32gui.EnumWindows = mock_enum_windows
result = self.manager.find_window_by_exe("ycgame.exe")
# 应该正常处理异常返回False
self.assertFalse(result)
@patch('src.core.game_window.win32gui')
def test_find_window_by_exe_exception(self, mock_win32gui):
"""测试异常情况处理"""
mock_win32gui.EnumWindows.side_effect = Exception("Test exception")
result = self.manager.find_window_by_exe("ycgame.exe")
self.assertFalse(result)
# ==================== 集成测试(需要实际游戏运行)====================
@unittest.skip("需要实际游戏运行")
def test_integration_find_real_game_window_by_exe(self):
"""集成测试查找真实游戏窗口通过exe"""
result = self.manager.find_window_by_exe("ycgame.exe")
if result:
print(f"\n找到游戏窗口:")
print(f" HWND: {self.manager.hwnd}")
print(f" PID: {self.manager.process_id}")
print(f" 大小: {self.manager.client_size}")
info = self.manager.get_window_info()
print(f" 标题: {info['title']}")
print(f" 路径: {info['exe_path']}")
else:
print("\n未找到游戏窗口,请确保游戏已运行")
# 不强制断言,因为游戏可能未运行
@unittest.skip("需要实际游戏运行")
def test_integration_find_real_game_window_by_title(self):
"""集成测试:查找真实游戏窗口(通过标题)"""
result = self.manager.find_window_by_title("桃源深处有人家")
if result:
print(f"\n找到游戏窗口:")
print(f" HWND: {self.manager.hwnd}")
print(f" 大小: {self.manager.client_size}")
else:
print("\n未找到游戏窗口,请确保游戏已运行")
@unittest.skip("需要实际游戏运行")
def test_integration_window_operations(self):
"""集成测试:窗口操作"""
# 先捕获窗口
if not self.manager.capture_window():
self.skipTest("游戏未运行,跳过测试")
# 测试获取信息
info = self.manager.get_window_info()
self.assertTrue(info["captured"])
# 测试置前(不验证结果,只是确保不报错)
try:
self.manager.bring_to_front()
except Exception as e:
self.fail(f"bring_to_front 抛出异常: {e}")
# 等待一下观察效果
time.sleep(1)
class TestGameWindowManagerPerformance(unittest.TestCase):
"""性能测试"""
@patch('src.core.game_window.win32gui')
@patch('src.core.game_window.win32process')
@patch('src.core.game_window.psutil')
def test_find_window_performance(self, mock_psutil, mock_win32process, mock_win32gui):
"""测试查找窗口性能"""
# 设置模拟数据
mock_win32gui.IsWindowVisible.return_value = True
mock_win32gui.GetWindowText.return_value = "桃源深处有人家"
mock_win32gui.GetWindowRect.return_value = (100, 100, 1100, 700)
mock_win32process.GetWindowThreadProcessId.return_value = (None, 12345)
mock_process = Mock()
mock_process.exe.return_value = "C:\\Game\\ycgame.exe"
mock_psutil.Process.return_value = mock_process
def mock_enum_windows(callback, extra):
for i in range(100): # 模拟100个窗口
callback(10000 + i, extra)
return True
mock_win32gui.EnumWindows = mock_enum_windows
manager = GameWindowManager()
# 测量查找时间
import time
start = time.time()
result = manager.find_window_by_exe("ycgame.exe")
elapsed = time.time() - start
self.assertTrue(result)
self.assertLess(elapsed, 1.0, "查找窗口应该在一秒内完成")
print(f"\n查找100个窗口耗时: {elapsed:.3f}")
if __name__ == "__main__":
# 运行测试
unittest.main(verbosity=2)