web-dev-qa-db-fra.com

Tests unitaires que les événements sont générés en C # (dans l'ordre)

J'ai du code qui soulève des événements PropertyChanged et j'aimerais pouvoir vérifier si les événements sont correctement déclenchés.

Le code qui soulève les événements est comme

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

Je reçois un bon test vert du code suivant dans mes tests unitaires, qui utilise des délégués:

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

Cependant, si j’essaie ensuite d’enchaîner le paramétrage des propriétés comme suit:

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

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

Mon test pour l'événement échoue - l'événement capturé est l'événement de MyOtherProperty.

Je suis presque sûr que l'événement se déclenche, mon interface utilisateur réagit comme il le fait, mais mon délégué ne capture que le dernier événement à déclencher.

Alors je me demande:
1. Ma méthode de test des événements est-elle correcte?
2. Est-ce que ma méthode de relance chaînée événements est correcte?

150
David Hall

Tout ce que vous avez fait est correct, à condition que vous souhaitiez que votre test demande "Quel est le dernier événement soulevé?"

Votre code déclenche ces deux événements, dans cet ordre

  • Propriété modifiée (... "Ma propriété" ...)
  • Propriété modifiée (... "MyOtherProperty" ...)

Que cela soit "correct" ou non dépend du but de ces événements.

Si vous souhaitez tester le nombre d'événements générés et leur ordre, vous pouvez facilement étendre votre test existant:

[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]);
}
176
Andrew Stapleton

Si vous utilisez TDD, les tests d’événements peuvent commencer à générer un lot de code répétitif. J'ai écrit un moniteur d'événements qui permet une approche beaucoup plus propre de l'écriture de test unitaire pour ces situations.

var publisher = new PropertyChangedEventPublisher();

Action test = () =>
{
    publisher.X = 1;
    publisher.Y = 2;
};

var expectedSequence = new[] { "X", "Y" };

EventMonitor.Assert(test, publisher, expectedSequence);

S'il vous plaît voir ma réponse à ce qui suit pour plus de détails.

L'unité teste qu'un événement est déclenché en C #, en utilisant la réflexion

19
Tim Lloyd

Ceci est très ancien et ne sera probablement même pas lu, mais avec quelques nouvelles fonctionnalités .net fraîches, j'ai créé une classe INPC Tracer qui permet:

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

Voir Gist: https://Gist.github.com/Seikilos/6224204

8
Samuel

Ci-dessous se trouve un code légèrement modifié d'Andrew qui, au lieu de simplement enregistrer la séquence d'événements déclenchés, compte plutôt combien de fois un événement spécifique a été appelé. Bien que basé sur son code, je le trouve plus utile dans mes tests.

[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
Damir Arh

Sur la base de cet article, j'ai créé cette aide d'assertion simple:

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

Avec cette méthode d'assistance, le test devient vraiment simple.

[TestMethod()]
public void Event_UserName_PropertyChangedWillBeFired()
{
    var user = new User();
    AssertPropertyChanged(user, (x) => x.UserName = "Bob", "UserName");
}
1
nico

Ne pas écrire un test pour chaque membre - c'est beaucoup de travail

(Peut-être que cette solution n'est pas parfaite pour toutes les situations - mais elle montre une voie possible. Vous devrez peut-être l'adapter à votre cas d'utilisation)

Il est possible d'utiliser la réflexion dans une bibliothèque pour vérifier si vos membres répondent tous correctement à votre événement de propriété modifié:

  • L'événement PropertyChanged est déclenché lors de l'accès au setter
  • L'événement est déclenché correctement (le nom de la propriété est égal à l'argument de l'événement déclenché)

Le code suivant peut être utilisé en tant que bibliothèque et montre comment tester la classe générique suivante

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

Les tests de votre classe peuvent maintenant être écrits en tant que. (Peut-être que vous voulez diviser le test en "événement est là" et "événement soulevé avec le nom correct" - vous pouvez le faire vous-même)

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

Classe

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

J'ai fait une extension ici:

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

Il y a l'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());

    }
0
Mr.B