代码之家  ›  专栏  ›  技术社区  ›  David Hall

在C中引发事件的单元测试#

  •  143
  • David Hall  · 技术社区  · 16 年前

    我有一些代码 PropertyChanged 事件和我希望能够单元测试事件是否正确引发。

    引发事件的代码如下

    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");
               }
           }
       }
    }
    

    在我的单元测试中,我从以下代码中得到了一个很好的绿色测试,它使用委托:

    [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);
    }
    

    但是,如果我尝试像这样将属性设置链接在一起:

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

    我对事件的测试失败-它捕获的事件是myotherproperty的事件。

    我很确定事件会触发,我的用户界面和它的反应一样,但是我的委托只捕获最后一个要触发的事件。

    所以我想知道:
    1。我测试事件的方法是否正确?
    2。是我养大的方法吗? 链式的 事件正确吗?

    7 回复  |  直到 6 年前
        1
  •  164
  •   David Hall    14 年前

    你所做的一切都是正确的,只要你想让你的测试问“最后一个被提出的事件是什么?”

    你的代码按这个顺序触发这两个事件

    • 属性已更改(…我的财产“…)
    • 属性已更改(…肌热性“…)

    这是否“正确”取决于这些事件的目的。

    如果要测试引发的事件数以及它们的引发顺序,可以轻松地扩展现有测试:

    [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]);
    }
    
        2
  •  19
  •   E. Earl    6 年前

    如果您正在进行TDD,那么事件测试可以开始生成 许多 重复的代码。我编写了一个事件监视器,它可以为这些情况下的单元测试编写提供更清晰的方法。

    var publisher = new PropertyChangedEventPublisher();
    
    Action test = () =>
    {
        publisher.X = 1;
        publisher.Y = 2;
    };
    
    var expectedSequence = new[] { "X", "Y" };
    
    EventMonitor.Assert(test, publisher, expectedSequence);
    

    有关更多详细信息,请参阅我对以下内容的回答。

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

        3
  •  7
  •   Samuel    11 年前

    这是非常古老的,甚至可能不会被阅读,但通过一些很酷的新.NET功能,我创建了一个inpc跟踪类,它允许:

    [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);
    }
    

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

        4
  •  5
  •   Damir Arh    14 年前

    下面是一个稍作改动的安德鲁代码,它不只是记录引发的事件序列,而是计算调用特定事件的次数。虽然它是基于他的代码,但我发现它在我的测试中更有用。

    [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"]);
    }
    
        5
  •  1
  •   OrionMD    6 年前

    不要为每个成员编写测试-这是很多工作

    (也许这个解决方案并不适用于所有情况——但它显示了一种可能的方法。您可能需要为您的用例调整它)

    可以使用库中的反射来测试成员是否都正确响应了属性更改事件:

    • Setter访问时引发PropertyChanged事件
    • 引发的事件正确(属性名等于引发的事件的参数)

    以下代码可以用作库,并演示如何测试以下泛型类

    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();
            }
        }
    

    现在可以将类的测试编写为。(也许您希望将测试拆分为“事件是否存在”和“用正确名称引发的事件”-您可以自己这样做)

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

    等级

    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");
                }
            }
    }
    
        6
  •  0
  •   nico    9 年前

    基于本文,我创建了这个简单断言助手:

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

    有了这个方法助手,测试变得非常简单。

    [TestMethod()]
    public void Event_UserName_PropertyChangedWillBeFired()
    {
        var user = new User();
        AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
    }
    
        7
  •  0
  •   Mr.B    8 年前

    我在这里做了一个扩展:

    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;
        }
    }
    

    有以下用法:

       [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());
    
        }