Модульное тестирование, что событие возникает в C#
у меня есть код, который вызывает 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");
}
}
}
мой тест для события терпит неудачу-событие, что это captures-это событие для MyOtherProperty.
Я уверен, что событие срабатывает, мой пользовательский интерфейс реагирует так же, как и он, но мой делегат фиксирует только последнее событие для запуска.
поэтому мне интересно:
1. Является ли мой метод тестирования событий правильным?
2. Это мой метод воспитания прикованный событий правильный?
7 ответов:
все, что вы сделали, правильно, если вы хотите, чтобы ваш тест спросил: "какое последнее событие было поднято?"
ваш код запускает эти два события, в таком порядке
- Свойство Изменилось (... "моя собственность. "..)
- Свойство Изменилось (... "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]); }
Если вы делаете TDD, то тестирование событий может начать генерировать много повторяющегося кода. Я написал монитор событий, который позволяет гораздо более чистый подход к написанию модульного теста для этих ситуаций.
var publisher = new PropertyChangedEventPublisher(); Action test = () => { publisher.X = 1; publisher.Y = 2; }; var expectedSequence = new[] { "X", "Y" }; EventMonitor.Assert(test, publisher, expectedSequence);пожалуйста, смотрите мой ответ на следующее Для получения более подробной информации.
модульное тестирование, что событие возникает в C#, используя отражение
или ряд статей в блоге, о которых я написал это:
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
Это очень старый и, вероятно, даже не будет читать, но с некоторыми новыми функциями .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); }
Ниже приведен немного измененный код Эндрю, который вместо того, чтобы просто регистрировать последовательность поднятых событий, а подсчитывает, сколько раз было вызвано конкретное событие. Хотя он основан на его коде, я нахожу его более полезным в своих тестах.
[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"]); }
основываясь на этой статье, я создал этот простой вспомогательное утверждение :
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"); }
Я сделал расширение здесь:
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()); }
не пишите тест для каждого члена-это много работы
(возможно, это решение не идеально подходит для каждой ситуации - но оно показывает возможный путь. Возможно, вам придется адаптировать его для вашего варианта использования)
можно использовать отражение в библиотеке, чтобы проверить, все ли ваши члены правильно реагируют на ваше событие изменения свойств:
- PropertyChanged событие вызывается на setter access
- событие возникает правильно (имя свойства равно аргументу возбужденного события)
следующий код может быть использован в качестве библиотеки и показывает, как проверить следующие общие класс
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"); } } }
Comments