简体   繁体   English

在C#中引发事件的单元测试(按顺序)

[英]Unit testing that events are raised in C# (in order)

I have some code that raises PropertyChanged events and I would like to be able to unit test that the events are being raised correctly. 我有一些引发PropertyChanged事件的代码,我希望能够对事件是否正确引发进行单元测试。

The code that is raising the events is like 引发事件的代码就像

public class MyClass : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;  

   protected void NotifyPropertyChanged(String info)
   {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
   }  

   public string MyProperty
   {
       set
       {
           if (_myProperty != value)
           {
               _myProperty = value;
               NotifyPropertyChanged("MyProperty");
           }
       }
   }
}

I get a nice green test from the following code in my unit tests, that uses delegates: 我从单元测试中的以下代码中得到了一个不错的绿色测试,该测试使用了委托:

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    string actual = null;
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
         actual = e.PropertyName;
    };

    myClass.MyProperty = "testing";
    Assert.IsNotNull(actual);
    Assert.AreEqual("MyProperty", actual);
}

However, if I then try and chain the setting of properties together like so: 但是,如果我尝试将属性设置链接在一起,如下所示:

public string MyProperty
{
    set
    {
        if (_myProperty != value)
        {
            _myProperty = value;
            NotifyPropertyChanged("MyProperty");
            MyOtherProperty = "SomeValue";
        }
    }
}

public string MyOtherProperty
{
    set
    {
        if (_myOtherProperty != value)
        {
            _myOtherProperty = value;
            NotifyPropertyChanged("MyOtherProperty");
        }
    }
}

My test for the event fails - the event that it captures is the event for the MyOtherProperty. 我对该事件的测试失败-它捕获的事件是MyOtherProperty的事件。

I'm pretty sure the event fires, my UI reacts like it does, but my delegate only captures the last event to fire. 我很确定事件会触发,我的UI会像它一样做出反应,但是我的委托人只捕获了要触发的最后一个事件。

So I'm wondering: 所以我想知道:
1. Is my method of testing events correct? 1.我测试事件的方法是否正确?
2. Is my method of raising chained events correct? 2.我提出连锁事件的方法正确吗?

Everything you've done is correct, providing you want your test to ask "What is the last event that was raised?" 您所做的一切都是正确的,只要您要测试询问“上一次引发的事件是什么?”

Your code is firing these two events, in this order 您的代码按以下顺序触发这两个事件

  • Property Changed (... "My Property" ...) 财产已更改(...“我的财产” ...)
  • Property Changed (... "MyOtherProperty" ...) 属性已更改(...“ MyOtherProperty” ...)

Whether this is "correct" or not depends upon the purpose of these events. 这是否“正确”取决于这些事件的目的。

If you want to test the number of events that gets raised, and the order they get raised in, you can easily extend your existing test: 如果要测试引发的事件数及其引发的顺序,则可以轻松扩展现有测试:

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    List<string> receivedEvents = new List<string>();
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
        receivedEvents.Add(e.PropertyName);
    };

    myClass.MyProperty = "testing";
    Assert.AreEqual(2, receivedEvents.Count);
    Assert.AreEqual("MyProperty", receivedEvents[0]);
    Assert.AreEqual("MyOtherProperty", receivedEvents[1]);
}

If you're doing TDD then event testing can start to generate a lot of repetitive code. 如果您正在执行TDD,则事件测试可以开始生成大量重复代码。 I wrote an event monitor that enables a much cleaner approach to unit test writing for these situations. 我编写了一个事件监视器,它为在这种情况下的单元测试编写提供了一种更为简洁的方法。

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

Please see my answer to the following for more details. 请参阅我对以下内容的回答以获取更多详细信息。

Unit testing that an event is raised in C#, using reflection 使用反射对C#中引发的事件进行单元测试

This is very old and probably wont even be read but with some cool new .net features I have created an INPC Tracer class that allows that: 这是非常古老的,甚至可能不会被读取,但是由于具有一些很棒的.net新功能,我创建了一个INPC Tracer类,该类允许以下操作:

[Test]
public void Test_Notify_Property_Changed_Fired()
{
    var p = new Project();

    var tracer = new INCPTracer();

    // One event
    tracer.With(p).CheckThat(() => p.Active = true).RaisedEvent(() => p.Active);

    // Two events in exact order
    tracer.With(p).CheckThat(() => p.Path = "test").RaisedEvent(() => p.Path).RaisedEvent(() => p.Active);
}

See gist: https://gist.github.com/Seikilos/6224204 参见要点: https : //gist.github.com/Seikilos/6224204

Below is a slightly changed Andrew's code which instead of just logging the sequence of raised events rather counts how many times a specific event has been called. 下面是稍作更改的安德鲁的代码,它不仅记录了引发事件的序列,还计算了一个特定事件被调用了多少次。 Although it is based on his code I find it more useful in my tests. 尽管它基于他的代码,但我发现它在我的测试中更有用。

[TestMethod]
public void Test_ThatMyEventIsRaised()
{
    Dictionary<string, int> receivedEvents = new Dictionary<string, int>();
    MyClass myClass = new MyClass();

    myClass.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e)
    {
        if (receivedEvents.ContainsKey(e.PropertyName))
            receivedEvents[e.PropertyName]++;
        else
            receivedEvents.Add(e.PropertyName, 1);
    };

    myClass.MyProperty = "testing";
    Assert.IsTrue(receivedEvents.ContainsKey("MyProperty"));
    Assert.AreEqual(1, receivedEvents["MyProperty"]);
    Assert.IsTrue(receivedEvents.ContainsKey("MyOtherProperty"));
    Assert.AreEqual(1, receivedEvents["MyOtherProperty"]);
}

Based on this article, i have created this simple assertion helper : 根据本文,我创建了这个简单的断言助手:

private void AssertPropertyChanged<T>(T instance, Action<T> actionPropertySetter, string expectedPropertyName) where T : INotifyPropertyChanged
    {
        string actual = null;
        instance.PropertyChanged += delegate (object sender, PropertyChangedEventArgs e)
        {
            actual = e.PropertyName;
        };
        actionPropertySetter.Invoke(instance);
        Assert.IsNotNull(actual);
        Assert.AreEqual(propertyName, actual);
    }

With this method helper, the test becomes really simple. 使用此方法帮助器,测试变得非常简单。

[TestMethod()]
public void Event_UserName_PropertyChangedWillBeFired()
{
    var user = new User();
    AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
}

Don't write a test for each member - this is much work 不要为每个成员编写测试-这是很多工作

(maybe this solution is not perfect for every situation - but it shows a possible way. You might need to adapt it for your use case) (也许此解决方案并非在每种情况下都是完美的-但它显示了一种可能的方法。您可能需要针对您的用例进行调整)

It's possible to use reflection in a library to test if your members are all responding to your property changed event correctly: 可以在库中使用反射来测试您的成员是否都正确响应了属性更改事件:

  • PropertyChanged event is raised on setter access 在Setter访问时引发PropertyChanged事件
  • Event is raised correct (name of property equals argument of raised event) 正确引发事件(属性名称等于引发事件的参数)

The following code can be used as a library and shows how to test the following generic class 以下代码可用作库,并显示如何测试以下通用类

using System.ComponentModel;
using System.Linq;

/// <summary>
/// Check if every property respons to INotifyPropertyChanged with the correct property name
/// </summary>
public static class NotificationTester
    {
        public static object GetPropertyValue(object src, string propName)
        {
            return src.GetType().GetProperty(propName).GetValue(src, null);
        }

        public static bool Verify<T>(T inputClass) where T : INotifyPropertyChanged
        {
            var properties = inputClass.GetType().GetProperties().Where(x => x.CanWrite);
            var index = 0;

            var matchedName = 0;
            inputClass.PropertyChanged += (o, e) =>
            {
                if (properties.ElementAt(index).Name == e.PropertyName)
                {
                    matchedName++;
                }

                index++;
            };

            foreach (var item in properties)
            { 
                // use setter of property
                item.SetValue(inputClass, GetPropertyValue(inputClass, item.Name));
            }

            return matchedName == properties.Count();
        }
    }

The tests of your class can now be written as. 您班级的测试现在可以编写为。 (maybe you want to split the test into "event is there" and "event raised with correct name" - you can do this yourself) (也许您想将测试分为“事件在那里”和“使用正确名称引发的事件”-您可以自己执行此操作)

[TestMethod]
public void EveryWriteablePropertyImplementsINotifyPropertyChangedCorrect()
{
    var viewModel = new TestMyClassWithINotifyPropertyChangedInterface();
    Assert.AreEqual(true, NotificationTester.Verify(viewModel));
}

Class

using System.ComponentModel;

public class TestMyClassWithINotifyPropertyChangedInterface : INotifyPropertyChanged
{
        public event PropertyChangedEventHandler PropertyChanged;

        protected void NotifyPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }

        private int id;

        public int Id
        {
            get { return id; }
            set { id = value;
                NotifyPropertyChanged("Id");
            }
        }
}

I've made an extension here: 我在这里做了扩展:

public static class NotifyPropertyChangedExtensions
{
    private static bool _isFired = false;
    private static string _propertyName;

    public static void NotifyPropertyChangedVerificationSettingUp(this INotifyPropertyChanged notifyPropertyChanged,
      string propertyName)
    {
        _isFired = false;
        _propertyName = propertyName;
        notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
    }

    private static void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == _propertyName)
        {
            _isFired = true;
        }
    }

    public static bool IsNotifyPropertyChangedFired(this INotifyPropertyChanged notifyPropertyChanged)
    {
        _propertyName = null;
        notifyPropertyChanged.PropertyChanged -= OnPropertyChanged;
        return _isFired;
    }
}

There is the usage: 有用法:

   [Fact]
    public void FilesRenameViewModel_Rename_Apply_Execute_Verify_NotifyPropertyChanged_If_Succeeded_Through_Extension_Test()
    {
        //  Arrange
        _filesViewModel.FolderPath = ConstFolderFakeName;
        _filesViewModel.OldNameToReplace = "Testing";
        //After the command's execution OnPropertyChanged for _filesViewModel.AllFilesFiltered should be raised
        _filesViewModel.NotifyPropertyChangedVerificationSettingUp(nameof(_filesViewModel.AllFilesFiltered));
        //Act
        _filesViewModel.ApplyRenamingCommand.Execute(null);
        // Assert
        Assert.True(_filesViewModel.IsNotifyPropertyChangedFired());

    }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM