You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

630 lines
22 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 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