.NET5.0 DEBUG模式下WeakReference相关功能如何实现可靠单元测试
可行测试方案及原理
问题根因
Debug模式下JIT会延长方法局部变量的生命周期,会将所有局部变量的GC根保留到方法执行结束,哪怕你手动将变量赋值为null,JIT仍然认为该变量处于有效作用域内,不会释放根引用,因此GC无法回收目标对象,这就是你原有测试代码在net5.0 Debug下始终失败的核心原因。
核心解决思路
将需要被GC回收的目标对象的强引用,放到独立的、不会被内联的方法作用域中创建,等该方法执行完成后,对应栈帧销毁,所有局部强引用会被自动清除,此时再触发GC就能稳定回收目标对象。
具体测试实现
1. 测试OnFieldUpdated正常转发逻辑
这个场景不需要触发GC,直接验证事件转发逻辑即可:
[Fact] public void OnFieldUpdated_Should_Forward_To_Listener_When_Alive() { // 准备测试数据 var entity = new Entity(); var mockListener = new Mock<IEntityListener>(); _ = new WeakEntityListener(entity, mockListener.Object); var eventArgs = new FieldUpdatedEventArgs(); // 触发事件,若事件为内部成员可通过反射触发,或给原项目添加InternalsVisibleTo特性开放给测试项目 entity.RaiseFieldUpdated(eventArgs); // 验证转发成功 mockListener.Verify(l => l.OnFieldUpdated(eventArgs), Times.Once); }
2. 测试CleanUp自动清理逻辑
这个场景需要控制作用域确保监听器可被回收:
// 禁止方法内联,确保作用域独立,方法执行完后局部强引用全部销毁 [MethodImpl(MethodImplOptions.NoInlining)] private static (Entity Entity, WeakReference<IEntityListener> WeakListener) CreateTestObjects() { var entity = new Entity(); var listener = new Mock<IEntityListener>().Object; _ = new WeakEntityListener(entity, listener); return (entity, new WeakReference<IEntityListener>(listener)); } [Fact] public void OnFieldUpdated_Should_CleanUp_When_Listener_Collected() { // 从独立方法获取对象,此时CreateTestObjects的栈帧已销毁,不存在listener的强引用 var (entity, weakListener) = CreateTestObjects(); // 强制全代GC回收,覆盖所有代和大对象堆 GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); GC.WaitForPendingFinalizers(); GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true, true); // 验证监听器已被回收 Assert.False(weakListener.TryGetTarget(out _)); // 触发事件,此时会执行CleanUp逻辑移除事件订阅 entity.RaiseFieldUpdated(new FieldUpdatedEventArgs()); // 验证事件委托已被清空,可直接判断事件或通过反射获取调用列表验证 Assert.Null(entity.FieldUpdated); }
额外兼容配置
如果需要兼容所有框架版本和编译模式,可以在测试项目的.csproj中添加如下配置,无需关闭分层编译:
<PropertyGroup Condition="'$(Configuration)' == 'Debug'"> <GCStressLevel>3</GCStressLevel> </PropertyGroup>
该配置会让Debug模式下GC的回收行为更激进,进一步保证测试稳定性。
内容的提问来源于stack exchange,提问作者Weifen Luo
相关产品推荐
相关产品推荐

