在 C# 中引发事件的单元测试(按顺序)
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/248989/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Unit testing that events are raised in C# (in order)
提问by David Hall
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?
2. Is my method of raising chainedevents correct?
所以我想知道:
1. 我测试事件的方法是否正确?
2. 我提出连锁事件的方法是否正确?
采纳答案by Andrew Stapleton
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]);
}
回答by Tim Lloyd
If you're doing TDD then event testing can start to generate a lotof repetitive code. I wrote an event monitor that enables a much cleaner approach to unit test writing for these situations.
如果您正在执行 TDD,那么事件测试可能会开始生成大量重复代码。我编写了一个事件监视器,它可以为这些情况提供更简洁的单元测试编写方法。
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
回答by Damir Arh
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.
下面是一个稍微改变的 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"]);
}
回答by Samuel
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
回答by nico
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");
}
回答by WhileTrueSleep
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
- Event is raised correct (name of property equals argument of raised event)
- 在 setter 访问时引发 PropertyChanged 事件
- 事件引发正确(属性名称等于引发事件的参数)
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");
}
}
}
回答by Mr.B
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());
}