如何测试验证事件访问器不会重复添加同一事件处理程序?
验证事件去重逻辑的测试方案
嘿,这个场景我之前做测试时也遇到过,咱们来聊聊可行的解决办法~
首先明确:反射确实是验证这类逻辑的常用方案,因为事件的调用列表是封装在类内部的,外部代码无法直接访问。不过也有更优雅的替代方案,咱们分情况说:
方案1:用反射直接获取事件的调用列表
如果你的类是用自定义事件访问器实现的去重(比如你自己维护了一个委托字段),步骤很清晰:
- 通过反射拿到类中存储事件委托的私有字段
- 获取该字段对应的委托实例
- 调用委托的
GetInvocationList()方法,检查返回的处理程序数组长度
举个代码例子:
假设你的业务类是这样的:
public class EventManager { private EventHandler _foo; public event EventHandler Foo { add { // 你的去重逻辑:避免重复添加同一处理程序 if (_foo == null || !_foo.GetInvocationList().Contains(value)) { _foo += value; } } remove { _foo -= value; } } }
对应的测试代码可以这么写:
[Test] public void FooEvent_DoesNotRegisterDuplicateHandlers() { // 准备测试对象和处理程序 var manager = new EventManager(); EventHandler testHandler = (sender, e) => { }; // 尝试两次注册同一个处理程序 manager.Foo += testHandler; manager.Foo += testHandler; // 用反射获取私有字段_foo var fooField = typeof(EventManager).GetField( "_foo", BindingFlags.Instance | BindingFlags.NonPublic ); var fooDelegate = fooField.GetValue(manager) as EventHandler; // 验证调用列表里只有一个处理程序 Assert.AreEqual(1, fooDelegate?.GetInvocationList().Length ?? 0); }
注意事项
如果是用默认事件实现(没有自定义add/remove访问器),编译器会自动生成一个类似<Foo>k__BackingField的私有字段,这时候可以通过字段类型过滤来找到它:
var fooField = typeof(EventManager) .GetFields(BindingFlags.Instance | BindingFlags.NonPublic) .First(f => f.FieldType == typeof(EventHandler));
方案2:为测试暴露内部方法(更优雅)
反射虽然可行,但会依赖类的内部实现细节(比如私有字段名),后续代码重构时容易导致测试失败。更推荐的方式是:
- 在业务类中添加一个内部方法,用来返回事件处理程序的数量
- 通过
InternalsVisibleTo特性,让测试项目可以访问这个内部方法
比如修改业务类:
// 在项目AssemblyInfo.cs里添加,允许测试项目访问内部成员 [assembly: InternalsVisibleTo("YourTestProjectName")] public class EventManager { private EventHandler _foo; public event EventHandler Foo { /* 你的去重逻辑 */ } // 供测试用的内部方法 internal int GetFooHandlerCount() { return _foo?.GetInvocationList().Length ?? 0; } }
测试代码就可以直接调用,完全不用反射:
[Test] public void FooEvent_DoesNotRegisterDuplicateHandlers() { var manager = new EventManager(); EventHandler testHandler = (sender, e) => { }; manager.Foo += testHandler; manager.Foo += testHandler; Assert.AreEqual(1, manager.GetFooHandlerCount()); }
这种方式更符合测试的“黑盒”原则,也更易维护。
内容的提问来源于stack exchange,提问作者MakePeaceGreatAgain
相关产品推荐
相关产品推荐

