簡體   English   中英

使用反射測試在C#中引發事件的單元

[英]Unit testing that an event is raised in C#, using reflection

我想測試設置某個屬性(或更一般地,執行一些代碼)會在我的對象上引發某個事件。 在這方面我的問題類似於單元測試,在C#中引發一個事件 ,但我需要很多這些測試而且我討厭樣板。 所以我正在尋找一種更通用的解決方案,使用反射。

理想情況下,我想做這樣的事情:

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

為了實現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);
}

如您所見,我的問題在於為此事件創建適當類型的Delegate 代理除了調用incrementer之外什么都不做。

由於C#中的所有語法糖漿,我對委托和事件如何真正起作用的概念有點模糊。 這也是我第一次涉足反思。 丟失的部分是什么?

使用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);
        }
    }
}

我最近寫了一系列關於發布同步和異步事件的對象的單元測試事件序列的博客文章。 這些帖子描述了單元測試方法和框架,並提供了完整的源代碼和測試。

我描述了一個“事件監視器”的實現,它允許編寫事件排序單元測試更清晰地編寫,即擺脫所有凌亂的樣板代碼。

使用我的文章中描述的事件監視器,可以像這樣編寫測試:

var publisher = new AsyncEventPublisher();

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

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

EventMonitor.Assert(publisher, test, expectedSequence);

或者對於實現INotifyPropertyChanged的類型:

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(publisher, test, expectedSequence);

對於原始問題中的情況:

MyClass myObject = new MyClass();
EventMonitor.Assert(myObject, () => { myObject.Width = 42; }, "Width");

EventMonitor執行所有繁重操作並將運行測試(操作)並斷言事件是按預期順序(expectedSequence)引發的。 它還會在測試失敗時輸出很好的診斷消息。 反射和IL用於獲取動態事件訂閱的工作,但這都是很好的封裝,所以只需要像上面這樣的代碼來編寫事件測試。

帖子中有很多細節描述了問題和方法,以及源代碼:

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

您提出的涵蓋所有案例的解決方案將非常難以實施。 但是,如果您願意接受不包含ref和out參數或返回值的委托類型,則應該能夠使用DynamicMethod。

在設計時,創建一個類來保存計數,讓我們稱之為CallCounter。

在AssertRaisesEvent中:

  • 創建CallCounter類的實例,將其保存在強類型變量中
  • 將計數初始化為零
  • 在你的計數器類中構造一個DynamicMethod

    new DynamicMethod(string.Empty, typeof(void), parameter types extracted from the eventInfo, typeof(CallCounter))

  • 獲取DynamicMethod的MethodBuilder並使用reflection.Emit添加用於遞增字段的操作碼

    • ldarg.0(this指針)
    • ldc_I4_1(一個常數)
    • ldarg.0(this指針)
    • ldfld(讀取計數的當前值)
    • stfld(將更新后的計數放回成員變量中)
  • 調用CreateDelegate的雙參數重載 ,第一個參數是從eventInfo中獲取的事件類型,第二個參數是您的CallCounter實例
  • 將生成的委托傳遞給eventInfo.AddEventHandler(你已經知道了)現在你已經准備好執行測試用例了(你已經有了這個)。
  • 最后以通常的方式閱讀計數。

我不能100%確定你要做的唯一一步是從EventInfo中獲取參數類型。 您使用EventHandlerType屬性然后? 好吧,在該頁面上有一個示例,顯示您只是為委托的Invoke方法獲取MethodInfo(我猜名稱“Invoke”在標准的某處保證)然后GetParameters然后提取所有ParameterType值,檢查一路上沒有ref / out參數。

這個怎么樣:

private void AssertRaisesEvent(Action action, object obj, string eventName)
    {
        EventInfo eventInfo = obj.GetType().GetEvent(eventName);
        int raisedCount = 0;
        EventHandler handler = new EventHandler((sender, eventArgs) => { ++raisedCount; });
        eventInfo.AddEventHandler(obj, handler );
        action.Invoke();
        eventInfo.RemoveEventHandler(obj, handler);

        Assert.AreEqual(1, raisedCount);
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM