ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Screenbox.Core.Tests 单元测试工程解析:SQLite 数据库、Legacy JSON 迁移与 Native AOT 兼容实践

Screenbox.Core.Tests 单元测试工程解析:SQLite 数据库、Legacy JSON 迁移与 Native AOT 兼容实践 音视频视频【免费下载链接】ScreenboxLibVLC-based media player for the Universal Windows Platform项目地址https://gitcode.com/gh_mirrors/sc/Screenbox点击查看免费下载Screenbox.Core.Tests 是 Screenbox基于 LibVLC 的 UWP 媒体播放器的核心层单元测试工程集中覆盖Screenbox.Core中的 SQLite 数据库服务DatabaseService、System.Text.Json 源生成序列化、旧版 JSON 数据到 SQL 的迁移以及 Native AOT 兼容性验证。阅读本篇后你将掌握该测试工程的目标与架构、构建与运行命令、以及为未来开发者/AI Agent 准备的三大关键约定源生成 JSON 元数据注册、WinRT 隔离与测试目录注入并能在源码层面理解每个测试背后的实现原理。一、测试工程定位测什么、为什么这么设计整个工程只依赖一个测试框架 TUnit并通过ProjectReference引用Screenbox.Core核心库见 Screenbox.Core.Tests/Screenbox.Core.Tests.csproj其测试目标被明确划分为五个方向SQL 数据库服务DatabaseServiceSQLite 表结构初始化校验library_folders、media_records、playback_progress、playlists、playlist_items五张表必须全部创建成功Schema 漂移检测与自动重建表媒体库缓存的保存与加载音乐/视频播放列表管理SavePlaylistAsync、LoadPlaylistAsync、ListPlaylistsAsync、DeletePlaylistAsync播放进度追踪的状态管理。Legacy JSON 迁移到 SQL验证旧版Playlists/*.json播放列表文件含PlaylistRecordDto与RawMediaRecordDto能正确导入playlists与playlist_items两张 SQLite 表迁移完成后的自动清理删除遗留.json文件与临时迁移产物。JSON 序列化与 System.Text.Json 源生成直接测试CoreJsonContextJsonSerializerContext源生成器保证 DTOPlaylistRecordDto、RawMediaRecordDto的序列化/反序列化全程零反射。Native AOT 兼容性测试工程配置 Roslyn AOT 与裁剪分析器IsAotCompatibletrue/IsAotCompatible、EnableTrimAnalyzertrue/EnableTrimAnalyzer、EnableSingleFileAnalyzertrue/EnableSingleFileAnalyzer。现代 C# 特性目标框架net10.0-windows10.0.26100.0测试文件全面使用 C# 12 特性文件范围命名空间、主构造函数、集合表达式[ item1, item2 ]、原始字符串字面量...、模式匹配与切片运算符。上述每一项都能在测试目录 Screenbox.Core.Tests/Database、Screenbox.Core.Tests/Serialization 与 Screenbox.Core.Tests/Contexts 中找到对应的真实用例下文将逐项深入。二、DatabaseService 测试从建表到数据往返2.1 建表验证与 Schema 漂移检测DatabaseServiceTests.cs 中的InitializeAsync_CreatesDatabaseAndAllRequiredTables是数据库测试的起点初始化DatabaseService后断言screenbox.db文件存在并通过sqlite_master查询逐一验证五张表均被创建。这背后对应 DatabaseService.Schema.cs 的初始化流程打开连接后先执行PRAGMA journal_modeWAL;WAL 模式与PRAGMA foreign_keysOFF;随后在单事务中建表EnsureReplaceableTable通过PRAGMA table_info(...)读取实际列用HashSet比较列集合是否与预期完全一致HasSchemaDrift即!actualColumns.SetEquals(expectedColumns)表不存在则直接建表列集合与预期不一致则判定为 Schema 漂移先DROP TABLE再重建建表失败SqliteException或IOException时RecreateDatabaseFile会删除损坏的screenbox.db连同-shm、-wal文件并全新重建。各表结构定义于同一文件DatabaseService.Schema.cs表主键/约束关键列library_foldersidAUTOINCREMENTpathUNIQUEpath,media_typemedia_recordspathPRIMARY KEYtitle,media_type,date_added,duration_ticks,year,artist,album,album_artist,composers,genre,track_number,bitrate,subtitle,producers,writers,width,height,video_bitrateplayback_progresslocationPRIMARY KEYposition_ticksplaylistsidPRIMARY KEYdisplay_name,last_updatedplaylist_itemsidAUTOINCREMENTplaylist_id引用playlistsON DELETE CASCADE,path,sort_order值得注意的是media_records用path作主键、duration_ticks以 ticks 存储时间、媒体类型以INTEGER存储这些设计均与RawMediaRecordDto的属性一一对应见 RawMediaRecordDto.cs。2.2 媒体库缓存的保存与加载SaveMusicCacheAsync_And_LoadLibraryCacheAsync_PersistsAndRetrievesMusicRecordsDatabaseServiceTests.cs与对应的视频版本L103-L146验证了完整的缓存往返构造两个MusicCacheRecordDto含Path、Title、Artist、Album、Composers、Genre、TrackNumber、Bitrate、DateAdded、Duration、Year等完整元数据后调用SaveMusicCacheAsync(folders, records)再通过LoadLibraryCacheAsync(MediaPlaybackType.Music)读回RawCacheLoadResultDto断言FolderPaths与Records的数量和字段值完全一致。重点场景重扫描清理过期记录。SaveMusicCacheAsync_ClearsStaleRecordsOnRescanL148-L179先保存两个文件夹的两首歌再只保存一个文件夹的一首歌模拟用户移除文件夹并刷新断言读回的记录只剩 1 条——这意味着保存操作会以最新扫描结果为准覆盖全量缓存而不是增量累积。2.3 播放列表 CRUDPlaylistOperations_SaveLoadListAndDelete_BehavesCorrectlyL214-L257按“保存 → 加载 → 列举 → 删除”完整走了一遍播放列表生命周期SavePlaylistAsync写入一个含 2 个条目的PlaylistRecordDtoLoadPlaylistAsync(pl_001)读回并断言Id、DisplayName、Items数量与路径ListPlaylistsAsync返回单元素列表DeletePlaylistAsync(pl_001)后再次加载返回null。PlaylistRecordDto的结构非常简单PlaylistRecordDto.csId、DisplayName、LastUpdated加一个ListRawMediaRecordDto Items与playlists/playlist_items两张表的拆分存储形成对应关系。2.4 播放进度追踪PlaybackProgressOperations_SaveAndLoad_RoundtripsPositionTicksL259-L279验证ReplacePlaybackProgressAsync写入快照后LoadPlaybackProgressAsync能按Location精确还原Position.Ticks示例中为 42.5 分钟支撑“从上次位置续播”这一核心播放器体验。三、Legacy JSON 迁移旧数据导入与自动清理3.1 迁移主流程LegacyMigrationTests.cs 中的InitializeAsync_MigratesLegacyJsonPlaylistsToSql_AndCleansUpLegacyFiles完整还原了迁移场景在临时目录创建Playlists文件夹写入一个含 2 条RawMediaRecordDto的 JSON 播放列表ClassicRock.json并额外放置遗留的songs.bin二进制文件用JsonSerializer.SerializeCoreJsonContext.Default.PlaylistRecordDto生成 JSON这里已经体现了“必须走源生成上下文”的约定初始化DatabaseService迁移在InitializeAsync时触发断言LoadPlaylistAsync(legacy_pl_123)能读到导入后的播放列表及其全部条目断言Playlists目录被删除、songs.bin被删除——清理动作完成。3.2 源码级迁移逻辑迁移实现在 DatabaseService.Schema.cs 的TryImportLegacyPlaylistsAsync触发条件playlists表当前无任何行TableHasRows为假且Playlists目录存在、目录内存在*.json文件——任一条件不满足就跳过迁移导入方式对每个 JSON 文件执行INSERT OR REPLACE INTO playlists ...随后清空该播放列表的playlist_items再按数组下标作为sort_order逐条INSERT健壮性处理DisplayName为空时回退用Id代替LastUpdated为default时使用DateTimeOffset.UtcNow条目Path为空则跳过单个文件解析失败只记录日志hasImportFailure true并继续处理其余文件清理时机只有全部文件导入成功migrationComplete后才调用TryDeleteLegacyMigrationArtifactsL247-L272删除songs.bin、videos.bin两个遗留文件及整个Playlists文件夹。文件常量定义在 DatabaseService.cs。3.3 幂等性保障InitializeAsync_WhenSqlDatabaseAlreadyHasPlaylists_DoesNotReimportLegacyFilesLegacyMigrationTests.cs验证了关键的幂等语义如果 SQL 库中已有播放列表哪怕只有一条再次初始化时遗留 JSON不会被重复导入避免用户数据被覆盖。四、JSON 序列化零反射的源生成上下文4.1 测试直接验证 DTO 往返JsonSerializationTests.cs 用两个用例锁定源生成序列化的正确性PlaylistRecordDto_SerializationAndDeserialization_UsingCoreJsonContext构造含嵌套RawMediaRecordDto的播放列表经CoreJsonContext.Default.PlaylistRecordDto序列化再反序列化逐字段断言RawMediaRecordDto_SerializationAndDeserialization_HandlesAllProperties一次性覆盖全部 18 个属性含Width、Height、VideoBitrate等视频字段验证 4K 视频元数据也能无损往返。4.2 CoreJsonContext 的实现注册全部 DTO 的上下文位于 CoreJsonContext.cs仅 12 行[Obsolete(Remove the class in version 1.0, after giving older versions enough time to migrate away from Protobuf.)] [JsonSourceGenerationOptions(GenerationMode JsonSourceGenerationMode.Metadata)] [JsonSerializable(typeof(PlaylistRecordDto))] [JsonSerializable(typeof(RawMediaRecordDto))] public sealed partial class CoreJsonContext : JsonSerializerContext { }两个值得注意的细节JsonSourceGenerationMode.Metadata模式生成JsonTypeInfoT元数据而非基于属性的快速路径配合测试与迁移代码中显式传入CoreJsonContext.Default.PlaylistRecordDto/RawMediaRecordDto确保序列化全程无反射调用——这正是 Native AOT 下可用的前提类被标记[Obsolete]并注释说明将在 1.0 版本移除——从源码结构可以推断当前 JSON 序列化主要服务于“旧版数据迁移”这一过渡用途迁移窗口关闭后相关 DTO 与上下文会被清理。五、Native AOT 兼容与测试环境隔离5.1 工程级 AOT 配置测试工程在 Screenbox.Core.Tests.csproj 中显式声明了 AOT 与裁剪分析配置配置项值含义TargetFrameworknet10.0-windows10.0.26100.0面向 Windows 10 26100Windows 11 24H2API 级别TargetPlatformMinVersion10.0.19041.0最低支持 Windows 10 2004Platformsx86;x64;arm64与 UWP 应用一致的三种架构故意不启用 AnyCPURuntimeIdentifierswin-x86;win-x64;win-arm64与发布配置文件Screenbox/Properties/PublishProfiles对齐IsAotCompatibletrue启用 Roslyn AOT 兼容分析EnableTrimAnalyzertrue启用裁剪Trimming分析器EnableSingleFileAnalyzertrue启用单文件发布分析器DisableRuntimeMarshallingtrue关闭运行时编组配合 AOT 场景IsTestProjecttrue声明为测试工程TUnit1.64.13唯一的测试框架依赖三个分析器IsAotCompatible/EnableTrimAnalyzer/EnableSingleFileAnalyzer从编译期就开始把守 AOT 兼容红线任何反射式 JSON 序列化、未标注的运行时 API 或无法裁剪的类型都会在构建期暴露而不是等到运行时崩溃。5.2 SQLite 原生库的初始化TestDirectoryFixtureTestDirectoryFixture.cs在静态构造函数中调用SQLitePCL.Batteries.Init()确保 SQLite 原生库在 Native AOT/单元测试环境下完成初始化随后基于Path.GetTempPath()下的ScreenboxTests/{Guid}创建唯一临时目录Dispose时尽力递归删除清理失败仅记录、不抛异常。所有数据库测试都通过using var fixture new TestDirectoryFixture()获得完全隔离的沙箱目录。5.3 为 JIT 隔离 WinRT 的约定单元测试运行在通用 .NET 环境缺少 UWP 运行时元素如ApplicationData.Current。为此核心库把 WinRT 调用隔离到独立方法并用[MethodImpl(MethodImplOptions.NoInlining)]标注使得测试执行路径不会触发 JIT 加载 UWP 引用从而避免PlatformNotSupportedException崩溃——这一点正是 README.md 中“Information for Future Developers / AI Agents”第二条约定的根源。同时DispatcherQueueTestHelperDispatcherQueueTestHelper.cs通过LibraryImport(CoreMessaging.dll)调用CreateDispatcherQueueController为当前线程创建 DispatcherQueue并设置DispatcherQueueSynchronizationContext配合 TestInitializer.cs 中 TUnit 的[BeforeEvery(HookType.Test)]钩子保证每个测试执行前都已具备可用的调度环境。[LibraryImport][StructLayout(LayoutKind.Sequential)]的写法本身也是 AOT 友好的 P/Invoke 风格。5.4 数据库目录注入数据库初始化时DatabaseService.DbFolderPath为空才回退到ApplicationData.Current.LocalFolder.Path见 DatabaseService.cs。测试正是利用这一点在构造DatabaseService后直接赋值为fixture.DirectoryPath把 SQLite 交互“按需”重定向到隔离的临时目录完全不触碰真实用户数据。测试中的固定写法为using var fixture new TestDirectoryFixture(); var dbService new DatabaseService(NullLoggerDatabaseService.Instance) { DbFolderPath fixture.DirectoryPath, }; await dbService.InitializeAsync();NullLoggerDatabaseService.Instance来自Microsoft.Extensions.Logging.Abstractions让日志不影响测试输出。六、构建与运行测试测试工程刻意对齐 UWP 的x64、ARM64、x86架构有意覆盖掉标准的AnyCPU配置——构建和运行都必须显式指定架构。6.1 用 MSBuild / Visual Studio 构建C:\Program Files\Microsoft Visual Studio\18\Community\MSBuild\Current\Bin\MSBuild.exe Screenbox.Core.Tests\Screenbox.Core.Tests.csproj /p:ConfigurationRelease /p:Platformx64 /restore说明命令中的18对应 README 所述 Visual Studio 202618.x的安装路径如本机安装路径不同请按实际调整/restore在构建前自动还原 NuGet 依赖。6.2 运行测试方式一通过dotnet run直接运行dotnet run --project Screenbox.Core.Tests/Screenbox.Core.Tests.csproj方式二运行构建产物中的测试可执行文件Screenbox.Core.Tests\bin\x64\Release\net10.0-windows10.0.26100.0\Screenbox.Core.Tests.exe由于工程被配置为OutputTypeExe自包含测试宿主所以可以直接执行编译出的 exe 来跑全部用例这也是 TUnit 这类“控制台宿主”测试框架的典型用法。七、未来开发者 / AI Agent 的三大关键约定最后README.md 专门为后续维护者沉淀了三条必须遵守的约定这里结合源码逐一展开约定一新 DTO 必须注册到 CoreJsonContext在Screenbox.Core中新增 DTO 或 JSON 契约时务必在 CoreJsonContext.cs 追加[JsonSerializable(typeof(YourDto))]。严禁使用不带JsonSerializerContext/JsonTypeInfoT元数据的反射式JsonSerializer.DeserializeT(json)——这是 Native AOT 与裁剪分析器的硬性要求违反会在构建期被IsAotCompatible分析直接拦截。约定二WinRT 调用必须隔离单元测试运行在通用 .NET 上没有 UWP 运行时元素如ApplicationData.Current。核心库的 WinRT 调用必须隔离进独立方法并加[MethodImpl(MethodImplOptions.NoInlining)]从而保证 JIT 不会在测试执行路径上加载 UWP 引用避免PlatformNotSupportedException。新增测试同样应避免直接触碰 WinRT API需要调度环境时复用DispatcherQueueTestHelper.EnsureDispatcherQueue()。约定三测试必须注入隔离数据库目录不要依赖 WinRT 解析数据库目录。测试中统一实例化DatabaseService后设置公开属性DbFolderPath TestDirectoryPath配合TestDirectoryFixture的临时目录把 SQLite 交互强制限制在易失的隔离临时目录中保证测试可重复、可并行、且绝不污染真实本地数据。八、总结Screenbox.Core.Tests 不只是一组单元测试它同时扮演着三重角色DatabaseService的行为规格建表、漂移重建、缓存覆盖、播放列表 CRUD、进度快照、旧版 Protobuf/JSON 数据迁移的回归防线以及 Native AOT 兼容性的编译期哨兵。其工程组织——TUnit 控制台宿主 显式架构 临时目录夹具 源生成 JSON 上下文——为在 UWP 项目上做可脱离运行时的高质量核心层测试提供了一个可直接复用的范本。后续维护者可沿 Screenbox.Core.Tests 目录继续扩展用例同时严格遵守 README 中沉淀的三条约定。赞分享音视频视频【免费下载链接】ScreenboxLibVLC-based media player for the Universal Windows Platform项目地址https://gitcode.com/gh_mirrors/sc/Screenbox点击查看免费下载相关推荐res-downloader 使用指南手把手嗅探下载视频号、网页网络资源res downloader 使用指南手把手嗅探下载视频号、网页网络资源 在微信里打开视频号想保存的教程视频没有下载按钮。我打开 res downloade桌面应用网络音视频Gopherize.me移动端适配指南响应式设计与用户体验优化Gopherize.me移动端适配指南响应式设计与用户体验优化 Gopherize.me是一款深受开发者喜爱的Gopher形象生成工具通过简单的交互即可创建SQLite-to-MySQL轻量级数据库迁移工具实践指南SQLite to MySQL轻量级数据库迁移工具实践指南 工具概述 SQLite to MySQL是一款开源的数据迁移脚本工具主要功能是将SQLite数据上一篇Kaneo 前端动画设计指南把 Apple 流体界面原则落地到 Web 项目下一篇中文BERT-wwm完整指南30分钟掌握全词掩码预训练技术创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表