使用反射测试在C#中引发事件的单元

Tho*_*mas 12 c# reflection events delegates unit-testing

我想测试设置某个属性(或更一般地,执行一些代码)会在我的对象上引发某个事件.在这方面我的问题类似于单元测试,在C#中引发一个事件,但我需要很多这些测试而且我讨厌样板.所以我正在寻找一种更通用的解决方案,使用反射.

理想情况下,我想做这样的事情:

[TestMethod]
public void TestWidth() {
    MyClass myObject = new MyClass();
    AssertRaisesEvent(() => { myObject.Width = 42; }, myObject, "WidthChanged");
}
Run Code Online (Sandbox Code Playgroud)

为了实现AssertRaisesEvent,我来到这里:

private void AssertRaisesEvent(Action action, object obj, string eventName)
{
    EventInfo eventInfo = obj.GetType().GetEvent(eventName);
    int raisedCount = 0;
    Action incrementer = () => { ++raisedCount; };
    Delegate handler = /* what goes here? */;

    eventInfo.AddEventHandler(obj, handler);
    action.Invoke();
    eventInfo.RemoveEventHandler(obj, handler);

    Assert.AreEqual(1, raisedCount);
}
Run Code Online (Sandbox Code Playgroud)

如您所见,我的问题在于Delegate为此事件创建适当的类型.代理除了调用之外什么都不做incrementer.

由于C#中的所有语法糖浆,我对委托和事件如何工作的概念有点模糊.这也是我第一次涉足反思.丢失的部分是什么?

Tho*_*mas 8

使用lambdas,您可以使用非常少的代码完成此操作.只需为事件分配一个lambda,并在处理程序中设置一个值.无需反思,您获得强烈的类型重构

[TestFixture]
public class TestClass
{
    [Test]
    public void TestEventRaised()
    {
        // arrange
        var called = false;

        var test = new ObjectUnderTest();
        test.WidthChanged += (sender, args) => called = true;

        // act
        test.Width = 42;

        // assert
        Assert.IsTrue(called);
    }

    private class ObjectUnderTest
    {
        private int _width;
        public event EventHandler WidthChanged;

        public int Width
        {
            get { return _width; }
            set
            {
                _width = value; OnWidthChanged();
            }
        }

        private void OnWidthChanged()
        {
            var handler = WidthChanged;
            if (handler != null)
                handler(this, EventArgs.Empty);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • +1这是一个不被重视的解决方案.它易于阅读,理解和调试.它避免了对复杂代码(MSIL,Reflection等)的依赖,并且几乎涵盖了自定义事件处理程序的所有边缘情况,或者其他任何东西.应该至少投得更高. (4认同)

Tim*_*oyd 8

我最近写了一系列关于发布同步和异步事件的对象的单元测试事件序列的博客文章.这些帖子描述了单元测试方法和框架,并提供了完整的源代码和测试.

我描述了一个"事件监视器"的实现,它允许编写事件排序单元测试更清晰地编写,即摆脱所有凌乱的样板代码.

使用我的文章中描述的事件监视器,可以像这样编写测试:

var publisher = new AsyncEventPublisher();

Action test = () =>
{
    publisher.RaiseA();
    publisher.RaiseB();
    publisher.RaiseC();
};

var expectedSequence = new[] { "EventA", "EventB", "EventC" };

EventMonitor.Assert(publisher, test, expectedSequence);
Run Code Online (Sandbox Code Playgroud)

或者对于实现INotifyPropertyChanged的类型:

var publisher = new PropertyChangedEventPublisher();

Action test = () =>
{
    publisher.X = 1;
    publisher.Y = 2;
};

var expectedSequence = new[] { "X", "Y" };

EventMonitor.Assert(publisher, test, expectedSequence);
Run Code Online (Sandbox Code Playgroud)

对于原始问题中的情况:

MyClass myObject = new MyClass();
EventMonitor.Assert(myObject, () => { myObject.Width = 42; }, "Width");
Run Code Online (Sandbox Code Playgroud)

EventMonitor执行所有繁重操作并将运行测试(操作)并断言事件是按预期顺序(expectedSequence)引发的.它还会在测试失败时输出很好的诊断消息.反射和IL用于获取动态事件订阅的工作,但这都是很好的封装,所以只需要像上面这样的代码来编写事件测试.

帖子中有很多细节描述了问题和方法,以及源代码:

http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/

  • 该源代码仍然可用吗?想看看我现在是否可以使用它来对事件处理程序进行单元测试:) (2认同)