Модульное тестирование, что событие возникает в 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. Это мой метод воспитания прикованный событий правильный?

692   7  

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

см. gist:https://gist.github.com/Seikilos/6224204

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

[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

    Ничего не найдено.