Модульное тестирование, что событие вызывается в C # - PullRequest
146 голосов
/ 30 октября 2008

У меня есть некоторый код, который вызывает 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.

Я почти уверен, что событие происходит, мой пользовательский интерфейс реагирует так же, как и он, но мой делегат захватывает только последнее событие, которое сработало.

Вот мне и интересно:
1. Является ли мой метод проверки событий правильным?
2. Правильно ли мой метод поднятия прикованных событий?

Ответы [ 7 ]

167 голосов
/ 30 октября 2008

Все, что вы сделали, правильно, если вы хотите, чтобы ваш тест спросил: «Какое последнее событие было поднято?»

Ваш код запускает эти два события, в этом порядке

  • Свойство изменено (... "Мое имущество" ...)
  • Свойство изменено (... "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]);
}
19 голосов
/ 23 апреля 2010

Если вы делаете TDD, тогда тестирование событий может начать генерировать лот повторяющегося кода. Я написал монитор событий, который позволяет гораздо чище подходить к написанию модульных тестов для этих ситуаций.

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

Для получения более подробной информации см. Мой ответ на следующие вопросы.

Модульное тестирование того, что событие вызывается в C #, с использованием отражения

8 голосов
/ 13 августа 2013

Это очень старый и, вероятно, даже не будет читаться, но с некоторыми новыми классными функциями .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);
}

См. Суть: https://gist.github.com/Seikilos/6224204

5 голосов
/ 07 декабря 2010

Ниже приведен слегка измененный код Эндрю, который вместо того, чтобы просто регистрировать последовательность вызванных событий, скорее подсчитывает, сколько раз было вызвано определенное событие. Хотя он основан на его коде, я считаю его более полезным в моих тестах.

[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"]);
}
1 голос
/ 14 января 2016

Не пишите тест для каждого участника - это большая работа

(возможно, это решение не идеально для каждой ситуации - но оно показывает возможный путь. Возможно, вам придется адаптировать его для вашего случая использования)

Можно использовать отражение в библиотеке, чтобы проверить, правильно ли все ваши участники реагируют на событие, измененное вашим имуществом:

  • Событие 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();
        }
    }

Тесты вашего класса теперь можно записать как. (может быть, вы хотите разделить тест на «событие есть» и «событие с правильным именем» - вы можете сделать это самостоятельно)

[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");
            }
        }
}
1 голос
/ 28 ноября 2015

На основе этой статьи я создал этот простой помощник утверждения:

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");
}
0 голосов
/ 17 февраля 2017

Я сделал расширение здесь:

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

    }
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...