test - Pruebas unitarias de que un evento se plantea en C#
unit test visual studio (7)
A continuación se muestra un código de Andrew ligeramente modificado que, en lugar de solo registrar la secuencia de eventos planteados, más bien cuenta cuántas veces se ha llamado a un evento específico. Aunque está basado en su código, lo encuentro más útil en mis pruebas.
[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"]);
}
Tengo un código que genera eventos PropertyChanged
y me gustaría poder probar de manera unitaria que los eventos se están levantando correctamente.
El código que está planteando los eventos es como
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");
}
}
}
}
Obtuve una prueba verde agradable del siguiente código en mis pruebas de unidad, que usa delegados:
[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);
}
Sin embargo, si trato de encadenar la configuración de las propiedades de esta forma:
public string MyProperty
{
set
{
if (_myProperty != value)
{
_myProperty = value;
NotifyPropertyChanged("MyProperty");
MyOtherProperty = "SomeValue";
}
}
}
public string MyOtherProperty
{
set
{
if (_myOtherProperty != value)
{
_myOtherProperty = value;
NotifyPropertyChanged("MyOtherProperty");
}
}
}
Mi prueba del evento falla; el evento que captura es el evento de MyOtherProperty.
Estoy bastante seguro de que el evento se dispara, mi UI reacciona como lo hace, pero mi delegado solo captura el último evento para disparar.
Entonces me pregunto:
1. ¿Mi método de prueba de eventos es correcto?
2. ¿Es correcto mi método para generar eventos encadenados ?
Basado en este artículo, he creado este simple aserrador de afirmaciones:
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);
}
Con este método de ayuda, la prueba se vuelve realmente simple.
[TestMethod()]
public void Event_UserName_PropertyChangedWillBeFired()
{
var user = new User();
AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
}
Esto es muy antiguo y probablemente ni siquiera se leerá, pero con algunas características nuevas de .NET he creado una clase INPC Tracer que permite que:
[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);
}
Ver gist: https://gist.github.com/Seikilos/6224204
He hecho una extensión aquí:
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;
}
}
Existe el uso:
[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());
}
No escriba una prueba para cada miembro, esto es mucho trabajo
(Quizás esta solución no sea perfecta para cada situación, pero muestra una posible forma. Puede que necesite adaptarla para su caso de uso)
Es posible utilizar la reflexión en una biblioteca para comprobar si todos sus miembros responden correctamente a su evento modificado por la propiedad:
- El evento PropertyChanged se genera en el acceso setter
- El evento se plantea correctamente (el nombre de la propiedad es igual al argumento del evento planteado)
El siguiente código se puede utilizar una biblioteca muestra cómo probar la clase gernic
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();
}
}
Las pruebas de tu clase ahora pueden escribirse como. (Tal vez quiera dividir la prueba en "el evento está allí" y "evento planteado con el nombre correcto"; puede hacerlo usted mismo)
[TestMethod]
public void EveryWriteablePropertyImpementsINotifyPropertyChangedCorrect()
{
var viewModel = new TestMyClassWithINotifyPropertyChangedInterface();
Assert.AreEqual(true, NotificationTester.Verify(viewModel));
}
Clase
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");
}
}
}
Si está haciendo TDD, las pruebas de eventos pueden comenzar a generar un gran número de códigos repetitivos. Escribí un monitor de eventos que permite un enfoque mucho más limpio para la escritura de pruebas unitarias para estas situaciones.
var publisher = new PropertyChangedEventPublisher();
Action test = () =>
{
publisher.X = 1;
publisher.Y = 2;
};
var expectedSequence = new[] { "X", "Y" };
EventMonitor.Assert(test, publisher, expectedSequence);
Por favor vea mi respuesta a lo siguiente para más detalles.
Pruebas unitarias de que un evento se plantea en C #, utilizando la reflexión
O una serie de artículos de blog que publiqué al respecto:
http://gojisoft.com/blog/2010/04/22/event-sequence-unit-testing-part-1/
Todo lo que has hecho es correcto, siempre que quieras que tu prueba pregunte "¿Cuál fue el último evento que se planteó?"
Su código está disparando estos dos eventos, en este orden
- Propiedad cambiada (... "Mi propiedad" ...)
- Propiedad modificada (... "MyOtherProperty" ...)
Si esto es "correcto" o no, depende del propósito de estos eventos.
Si desea probar el número de eventos que se generan y el orden en que se plantearon, puede extender fácilmente su prueba existente:
[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]);
}