# P2P Network Communication - Media Player Tests """ 媒体播放器模块测试 测试音频和视频播放功能 需求: 6.1, 6.2, 6.3, 6.4, 6.5, 6.7 """ import os import tempfile import time import pytest from pathlib import Path from unittest.mock import MagicMock, patch from client.media_player import ( AudioPlayer, VideoPlayer, MediaPlayer, MediaType, PlaybackState, AudioFormat, VideoFormat, MediaInfo, MediaPlayerError, MediaNotFoundError, UnsupportedMediaFormatError, PlaybackError, MediaLoadError, AUDIO_EXTENSIONS, VIDEO_EXTENSIONS, ) from config import ClientConfig # ==================== Fixtures ==================== @pytest.fixture def audio_player(): """创建音频播放器实例""" player = AudioPlayer() yield player player.release() @pytest.fixture def video_player(): """创建视频播放器实例""" player = VideoPlayer() yield player player.release() @pytest.fixture def media_player(): """创建统一媒体播放器实例""" player = MediaPlayer() yield player player.release() @pytest.fixture def temp_audio_file(): """创建临时音频文件""" with tempfile.NamedTemporaryFile(suffix='.mp3', delete=False) as f: # 写入更多虚拟数据模拟音频文件(约1MB,估算约60秒) f.write(b'\xff\xfb\x90\x00' + b'\x00' * (1024 * 1024)) # MP3 header + data temp_path = f.name yield temp_path if os.path.exists(temp_path): os.unlink(temp_path) @pytest.fixture def temp_video_file(): """创建临时视频文件""" with tempfile.NamedTemporaryFile(suffix='.mp4', delete=False) as f: # 写入更多虚拟数据模拟视频文件(约5MB,估算约8秒) f.write(b'\x00\x00\x00\x1c\x66\x74\x79\x70' + b'\x00' * (5 * 1024 * 1024)) # MP4 header + data temp_path = f.name yield temp_path if os.path.exists(temp_path): os.unlink(temp_path) # ==================== AudioPlayer Tests (需求 6.1, 6.3, 6.5) ==================== class TestAudioPlayer: """音频播放器测试类""" def test_init(self, audio_player): """测试初始化""" assert audio_player.state == PlaybackState.STOPPED assert audio_player.position == 0.0 assert audio_player.duration == 0.0 assert audio_player.volume == 1.0 assert audio_player.current_file is None def test_detect_format_mp3(self, audio_player): """测试MP3格式检测 (需求 6.1)""" assert audio_player.detect_format("test.mp3") == AudioFormat.MP3 assert audio_player.detect_format("test.MP3") == AudioFormat.MP3 def test_detect_format_wav(self, audio_player): """测试WAV格式检测 (需求 6.1)""" assert audio_player.detect_format("test.wav") == AudioFormat.WAV def test_detect_format_aac(self, audio_player): """测试AAC格式检测 (需求 6.1)""" assert audio_player.detect_format("test.aac") == AudioFormat.AAC assert audio_player.detect_format("test.m4a") == AudioFormat.AAC def test_detect_format_flac(self, audio_player): """测试FLAC格式检测 (需求 6.1)""" assert audio_player.detect_format("test.flac") == AudioFormat.FLAC def test_detect_format_unknown(self, audio_player): """测试未知格式检测""" assert audio_player.detect_format("test.xyz") == AudioFormat.UNKNOWN def test_is_supported_format(self, audio_player): """测试格式支持检查 (需求 6.1)""" assert audio_player.is_supported_format("test.mp3") is True assert audio_player.is_supported_format("test.wav") is True assert audio_player.is_supported_format("test.aac") is True assert audio_player.is_supported_format("test.flac") is True assert audio_player.is_supported_format("test.xyz") is False def test_load_audio_file_not_found(self, audio_player): """测试加载不存在的文件""" with pytest.raises(MediaNotFoundError): audio_player.load_audio("nonexistent.mp3") def test_load_audio_unsupported_format(self, audio_player): """测试加载不支持的格式""" with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as f: f.write(b'test data') temp_path = f.name try: with pytest.raises(UnsupportedMediaFormatError): audio_player.load_audio(temp_path) finally: os.unlink(temp_path) def test_load_audio_success(self, audio_player, temp_audio_file): """测试成功加载音频文件 (需求 6.1)""" result = audio_player.load_audio(temp_audio_file) assert result is True assert audio_player.state == PlaybackState.STOPPED assert audio_player.current_file == temp_audio_file assert audio_player.media_info is not None assert audio_player.media_info.media_type == MediaType.AUDIO def test_play_without_load(self, audio_player): """测试未加载时播放""" with pytest.raises(PlaybackError): audio_player.play() def test_play_pause_stop(self, audio_player, temp_audio_file): """测试播放控制 (需求 6.3, 6.5)""" audio_player.load_audio(temp_audio_file) # 播放 audio_player.play() assert audio_player.state == PlaybackState.PLAYING assert audio_player.is_playing is True # 暂停 audio_player.pause() assert audio_player.state == PlaybackState.PAUSED assert audio_player.is_paused is True # 恢复播放 audio_player.play() assert audio_player.state == PlaybackState.PLAYING # 停止 audio_player.stop() assert audio_player.state == PlaybackState.STOPPED # 停止后位置重置为0 assert audio_player.position == 0.0 def test_seek(self, audio_player, temp_audio_file): """测试进度跳转 (需求 6.5)""" audio_player.load_audio(temp_audio_file) # 跳转到指定位置(在停止状态下) audio_player.seek(5.0) assert audio_player.position == 5.0 # 跳转到超出范围的位置应该被限制 audio_player.seek(-1.0) assert audio_player.position == 0.0 # 跳转到超过时长的位置应该被限制到时长 audio_player.seek(audio_player.duration + 10.0) assert audio_player.position == audio_player.duration def test_set_volume(self, audio_player): """测试音量控制 (需求 6.5)""" # 正常范围 audio_player.set_volume(0.5) assert audio_player.volume == 0.5 # 超出范围应该被限制 audio_player.set_volume(1.5) assert audio_player.volume == 1.0 audio_player.set_volume(-0.5) assert audio_player.volume == 0.0 def test_state_callback(self, audio_player, temp_audio_file): """测试状态回调""" states = [] audio_player.set_state_callback(lambda s: states.append(s)) audio_player.load_audio(temp_audio_file) audio_player.play() audio_player.stop() assert PlaybackState.LOADING in states assert PlaybackState.STOPPED in states assert PlaybackState.PLAYING in states def test_get_supported_formats(self, audio_player): """测试获取支持的格式列表""" formats = audio_player.get_supported_formats() assert 'mp3' in formats assert 'wav' in formats assert 'aac' in formats assert 'flac' in formats def test_get_supported_extensions(self, audio_player): """测试获取支持的扩展名列表""" extensions = audio_player.get_supported_extensions() assert '.mp3' in extensions assert '.wav' in extensions # ==================== VideoPlayer Tests (需求 6.2, 6.4, 6.7) ==================== class TestVideoPlayer: """视频播放器测试类""" def test_init(self, video_player): """测试初始化""" assert video_player.state == PlaybackState.STOPPED assert video_player.position == 0.0 assert video_player.duration == 0.0 assert video_player.volume == 1.0 assert video_player.current_file is None assert video_player.is_fullscreen is False def test_detect_format_mp4(self, video_player): """测试MP4格式检测 (需求 6.2)""" assert video_player.detect_format("test.mp4") == VideoFormat.MP4 assert video_player.detect_format("test.MP4") == VideoFormat.MP4 def test_detect_format_avi(self, video_player): """测试AVI格式检测 (需求 6.2)""" assert video_player.detect_format("test.avi") == VideoFormat.AVI def test_detect_format_mkv(self, video_player): """测试MKV格式检测 (需求 6.2)""" assert video_player.detect_format("test.mkv") == VideoFormat.MKV def test_detect_format_mov(self, video_player): """测试MOV格式检测 (需求 6.2)""" assert video_player.detect_format("test.mov") == VideoFormat.MOV def test_detect_format_unknown(self, video_player): """测试未知格式检测""" assert video_player.detect_format("test.xyz") == VideoFormat.UNKNOWN def test_is_supported_format(self, video_player): """测试格式支持检查 (需求 6.2)""" assert video_player.is_supported_format("test.mp4") is True assert video_player.is_supported_format("test.avi") is True assert video_player.is_supported_format("test.mkv") is True assert video_player.is_supported_format("test.mov") is True assert video_player.is_supported_format("test.xyz") is False def test_load_video_file_not_found(self, video_player): """测试加载不存在的文件""" with pytest.raises(MediaNotFoundError): video_player.load_video("nonexistent.mp4") def test_load_video_unsupported_format(self, video_player): """测试加载不支持的格式""" with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as f: f.write(b'test data') temp_path = f.name try: with pytest.raises(UnsupportedMediaFormatError): video_player.load_video(temp_path) finally: os.unlink(temp_path) def test_load_video_success(self, video_player, temp_video_file): """测试成功加载视频文件 (需求 6.2)""" result = video_player.load_video(temp_video_file) assert result is True assert video_player.state == PlaybackState.STOPPED assert video_player.current_file == temp_video_file assert video_player.media_info is not None assert video_player.media_info.media_type == MediaType.VIDEO def test_play_without_load(self, video_player): """测试未加载时播放""" with pytest.raises(PlaybackError): video_player.play() def test_play_pause_stop(self, video_player, temp_video_file): """测试播放控制 (需求 6.4, 6.5)""" video_player.load_video(temp_video_file) # 播放 video_player.play() assert video_player.state == PlaybackState.PLAYING assert video_player.is_playing is True # 暂停 video_player.pause() assert video_player.state == PlaybackState.PAUSED assert video_player.is_paused is True # 恢复播放 video_player.play() assert video_player.state == PlaybackState.PLAYING # 停止 video_player.stop() assert video_player.state == PlaybackState.STOPPED assert video_player.position == 0.0 def test_seek(self, video_player, temp_video_file): """测试进度跳转 (需求 6.5)""" video_player.load_video(temp_video_file) # 跳转到指定位置(在停止状态下) video_player.seek(5.0) assert video_player.position == 5.0 # 跳转到超出范围的位置应该被限制 video_player.seek(-1.0) assert video_player.position == 0.0 # 跳转到超过时长的位置应该被限制到时长 video_player.seek(video_player.duration + 10.0) assert video_player.position == video_player.duration def test_set_volume(self, video_player): """测试音量控制 (需求 6.5)""" # 正常范围 video_player.set_volume(0.5) assert video_player.volume == 0.5 # 超出范围应该被限制 video_player.set_volume(1.5) assert video_player.volume == 1.0 video_player.set_volume(-0.5) assert video_player.volume == 0.0 def test_fullscreen(self, video_player, temp_video_file): """测试全屏模式 (需求 6.7)""" video_player.load_video(temp_video_file) # 启用全屏 video_player.set_fullscreen(True) assert video_player.is_fullscreen is True # 禁用全屏 video_player.set_fullscreen(False) assert video_player.is_fullscreen is False # 切换全屏 result = video_player.toggle_fullscreen() assert result is True assert video_player.is_fullscreen is True result = video_player.toggle_fullscreen() assert result is False assert video_player.is_fullscreen is False def test_video_size(self, video_player, temp_video_file): """测试视频尺寸获取""" video_player.load_video(temp_video_file) width, height = video_player.video_size assert width > 0 assert height > 0 def test_get_supported_formats(self, video_player): """测试获取支持的格式列表""" formats = video_player.get_supported_formats() assert 'mp4' in formats assert 'avi' in formats assert 'mkv' in formats assert 'mov' in formats def test_get_supported_extensions(self, video_player): """测试获取支持的扩展名列表""" extensions = video_player.get_supported_extensions() assert '.mp4' in extensions assert '.avi' in extensions # ==================== MediaPlayer Tests (统一接口) ==================== class TestMediaPlayer: """统一媒体播放器测试类""" def test_init(self, media_player): """测试初始化""" assert media_player.state == PlaybackState.STOPPED assert media_player.media_type is None def test_detect_media_type_audio(self, media_player): """测试音频类型检测""" assert media_player.detect_media_type("test.mp3") == MediaType.AUDIO assert media_player.detect_media_type("test.wav") == MediaType.AUDIO assert media_player.detect_media_type("test.aac") == MediaType.AUDIO assert media_player.detect_media_type("test.flac") == MediaType.AUDIO def test_detect_media_type_video(self, media_player): """测试视频类型检测""" assert media_player.detect_media_type("test.mp4") == MediaType.VIDEO assert media_player.detect_media_type("test.avi") == MediaType.VIDEO assert media_player.detect_media_type("test.mkv") == MediaType.VIDEO assert media_player.detect_media_type("test.mov") == MediaType.VIDEO def test_detect_media_type_unknown(self, media_player): """测试未知类型检测""" assert media_player.detect_media_type("test.xyz") == MediaType.UNKNOWN def test_is_supported(self, media_player): """测试格式支持检查""" assert media_player.is_supported("test.mp3") is True assert media_player.is_supported("test.mp4") is True assert media_player.is_supported("test.xyz") is False def test_load_audio(self, media_player, temp_audio_file): """测试加载音频""" result = media_player.load_audio(temp_audio_file) assert result is True assert media_player.media_type == MediaType.AUDIO def test_load_video(self, media_player, temp_video_file): """测试加载视频""" result = media_player.load_video(temp_video_file) assert result is True assert media_player.media_type == MediaType.VIDEO def test_auto_load_audio(self, media_player, temp_audio_file): """测试自动检测加载音频""" result = media_player.load(temp_audio_file) assert result is True assert media_player.media_type == MediaType.AUDIO def test_auto_load_video(self, media_player, temp_video_file): """测试自动检测加载视频""" result = media_player.load(temp_video_file) assert result is True assert media_player.media_type == MediaType.VIDEO def test_auto_load_unsupported(self, media_player): """测试自动加载不支持的格式""" with tempfile.NamedTemporaryFile(suffix='.xyz', delete=False) as f: f.write(b'test data') temp_path = f.name try: with pytest.raises(UnsupportedMediaFormatError): media_player.load(temp_path) finally: os.unlink(temp_path) def test_play_without_load(self, media_player): """测试未加载时播放""" with pytest.raises(PlaybackError): media_player.play() def test_playback_controls_audio(self, media_player, temp_audio_file): """测试音频播放控制""" media_player.load_audio(temp_audio_file) media_player.play() assert media_player.is_playing is True media_player.pause() # 给一点时间让状态更新 time.sleep(0.05) assert media_player.is_paused is True media_player.stop() assert media_player.state == PlaybackState.STOPPED def test_playback_controls_video(self, media_player, temp_video_file): """测试视频播放控制""" media_player.load_video(temp_video_file) media_player.play() assert media_player.is_playing is True media_player.pause() # 给一点时间让状态更新 time.sleep(0.05) assert media_player.is_paused is True media_player.stop() assert media_player.state == PlaybackState.STOPPED def test_get_supported_formats(self, media_player): """测试获取支持的格式列表""" audio_formats = media_player.get_supported_audio_formats() video_formats = media_player.get_supported_video_formats() all_extensions = media_player.get_all_supported_extensions() assert 'mp3' in audio_formats assert 'mp4' in video_formats assert '.mp3' in all_extensions assert '.mp4' in all_extensions def test_release(self, media_player, temp_audio_file): """测试资源释放""" media_player.load_audio(temp_audio_file) media_player.play() media_player.release() assert media_player.state == PlaybackState.STOPPED # ==================== 属性测试 (需求 6.5) ==================== class TestPlaybackStateTransitions: """播放状态转换测试 - 属性 8: 媒体播放器控制一致性""" def test_audio_state_transitions(self, audio_player, temp_audio_file): """ 测试音频播放器状态转换的确定性 属性 8: 媒体播放器控制一致性 *对于任意*媒体播放状态,播放、暂停、进度跳转操作应该正确改变播放器状态, 且状态转换应该是确定性的。 验证: 需求 6.5 """ audio_player.load_audio(temp_audio_file) # STOPPED -> PLAYING audio_player.play() assert audio_player.state == PlaybackState.PLAYING # PLAYING -> PAUSED audio_player.pause() assert audio_player.state == PlaybackState.PAUSED # PAUSED -> PLAYING audio_player.play() assert audio_player.state == PlaybackState.PLAYING # PLAYING -> STOPPED audio_player.stop() assert audio_player.state == PlaybackState.STOPPED # STOPPED -> PLAYING -> STOPPED audio_player.play() audio_player.stop() assert audio_player.state == PlaybackState.STOPPED def test_video_state_transitions(self, video_player, temp_video_file): """ 测试视频播放器状态转换的确定性 属性 8: 媒体播放器控制一致性 验证: 需求 6.5 """ video_player.load_video(temp_video_file) # STOPPED -> PLAYING video_player.play() assert video_player.state == PlaybackState.PLAYING # PLAYING -> PAUSED video_player.pause() # 给一点时间让状态更新 time.sleep(0.05) assert video_player.state == PlaybackState.PAUSED # PAUSED -> PLAYING video_player.play() assert video_player.state == PlaybackState.PLAYING # PLAYING -> STOPPED video_player.stop() assert video_player.state == PlaybackState.STOPPED def test_idempotent_operations(self, audio_player, temp_audio_file): """ 测试幂等操作 多次调用相同操作应该保持状态一致 """ audio_player.load_audio(temp_audio_file) # 多次播放 audio_player.play() audio_player.play() assert audio_player.state == PlaybackState.PLAYING # 多次暂停 audio_player.pause() audio_player.pause() assert audio_player.state == PlaybackState.PAUSED # 多次停止 audio_player.stop() audio_player.stop() assert audio_player.state == PlaybackState.STOPPED