在C#(按顺序)中引发事件的单元测试
我有一些引发在C#(按顺序)中引发事件的单元测试,c#,unit-testing,events,C#,Unit Testing,Events,我有一些引发PropertyChanged事件的代码,我希望能够单元测试事件是否正确引发 引发事件的代码如下 public class MyClass : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(String info) { if (PropertyChang
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的事件
我很确定事件会触发,我的UI会像它一样做出反应,但我的委托只捕获最后一个要触发的事件
所以我想知道:1.我测试事件的方法正确吗?
2.我提出连锁事件的方法正确吗 您所做的一切都是正确的,前提是您希望测试询问“最后引发的事件是什么?” 您的代码按以下顺序触发这两个事件
- 属性已更改(…“我的属性”…)
- 属性已更改(…“MyOtherProperty”…)
[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]);
}
[TestMethod]
公共无效性测试
{
List receivedEvents=新列表();
MyClass MyClass=新的MyClass();
myClass.PropertyChanged+=委托(对象发送方,PropertyChangedEventArgs e)
{
receivedEvents.Add(如PropertyName);
};
myClass.MyProperty=“测试”;
aresequal(2,receivedEvents.Count);
AreEqual(“MyProperty”,receivedEvents[0]);
Assert.AreEqual(“MyOtherProperty”,receivedEvents[1]);
}
如果您正在进行TDD,那么事件测试可以开始生成大量重复代码。我编写了一个事件监视器,它为这些情况下的单元测试编写提供了一种更干净的方法
var publisher = new PropertyChangedEventPublisher();
Action test = () =>
{
publisher.X = 1;
publisher.Y = 2;
};
var expectedSequence = new[] { "X", "Y" };
EventMonitor.Assert(test, publisher, expectedSequence);
更多详情请参见我对以下内容的回答
下面是一个稍加修改的Andrew代码,它不只是记录引发事件的序列,而是计算特定事件被调用的次数。虽然它是基于他的代码,但我发现它在我的测试中更有用
[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"]);
}
[TestMethod]
公共无效性测试
{
Dictionary receivedEvents=新字典();
MyClass MyClass=新的MyClass();
myClass.PropertyChanged+=委托(对象发送方,PropertyChangedEventArgs e)
{
if(receivedEvents.ContainsKey(e.PropertyName))
receivedEvents[e.PropertyName]++;
其他的
receivedEvents.Add(如PropertyName,1);
};
myClass.MyProperty=“测试”;
Assert.IsTrue(receivedEvents.ContainsKey(“我的财产”);
aresequal(1,receivedEvents[“MyProperty”]);
Assert.IsTrue(receivedEvents.ContainsKey(“MyOtherProperty”);
Assert.AreEqual(1,receivedEvents[“MyOtherProperty”]);
}
这是一个非常古老的版本,甚至可能不会被阅读,但通过一些很酷的新.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);
}
请参阅要点:基于本文,我创建了这个简单的断言助手:
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);
}
不要为每个成员编写测试-这是一项艰巨的工作 (也许这个解决方案并不适合所有情况,但它展示了一种可能的方法。您可能需要根据您的用例调整它) 可以使用库中的反射来测试您的成员是否都正确响应属性更改事件:
- 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();
}
}
阶级
我在这里做了一个扩展:
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());
}
较短版本:myClass.PropertyChanged+=(对象发送者,e)=>receivedEvents.Add(e.PropertyName);第二个链接已关闭。我尝试了这个方法,但是如果我的属性设置器都有一个像“if(value==\u myValue)return”这样的保护语句(我所有的属性设置器都有),那么上面的方法就不会起作用,除非我遗漏了什么。我最近从C++到C语言。漂亮-你应该考虑打包它,并把它发布到NuGig.OrgWork工作!我非常喜欢fluent的API。我自己也做过类似的事情(),但我认为你的方法更简洁。
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");
}
}
}
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());
}