[c#] 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");
        }
    }
}

이벤트에 대한 내 테스트가 실패합니다. 캡처하는 이벤트는 MyOtherProperty의 이벤트입니다.

이벤트가 발생한다고 확신하고 UI가 반응하는 것처럼 반응하지만 내 대리인은 마지막 이벤트 만 캡처합니다.

그래서 궁금합니다 :
1. 이벤트 테스트 방법이 올바른가요?
2. 연쇄 사건 을 제기하는 방법이 올바른가?



답변

테스트에 “마지막으로 발생한 이벤트는 무엇입니까?”

코드에서이 두 이벤트를이 순서대로 실행합니다

  • 속성이 변경됨 (… “내 속성”…)
  • 속성이 변경됨 (… “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 #에서 이벤트가 발생하는지 단위 테스트


답변

이것은 매우 오래되었고 아마도 읽지 못할 수도 있지만 멋진 새로운 .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


답변

아래는 약간의 변경된 Andrew 코드로, 발생한 이벤트 시퀀스를 기록하는 대신 특정 이벤트가 호출 된 횟수를 계산합니다. 그의 코드를 기반으로하지만 내 테스트에서 더 유용하다는 것을 알았습니다.

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


답변

각 회원에 대한 테스트를 작성하지 마십시오-이것은 많은 작업입니다

(이 솔루션은 모든 상황에 완벽하지는 않지만 가능한 방법을 보여줍니다. 사용 사례에 맞게 조정해야 할 수도 있습니다)

라이브러리에서 리플렉션을 사용하여 멤버가 모두 속성 변경 이벤트에 올바르게 응답하는지 테스트 할 수 있습니다.

  • Setter 액세스시 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");
            }
        }
}


답변

여기에 확장을 만들었습니다.

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

    }