Olayların C # ile sıralandığını gösteren birim test (sırayla)


160

PropertyChangedOlayları yükselten bazı kod var ve ben olayları doğru bir şekilde yükseltiliyor birim test etmek istiyorum.

Olayları gündeme getiren kod şöyle

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

Birim testlerimde delegeleri kullanan aşağıdaki koddan güzel bir yeşil test alıyorum:

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

Ancak, ben sonra denemek ve özellikleri birlikte böyle zincir:

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

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

Olay için testim başarısız - yakaladığı olay MyOtherProperty olayıdır.

Etkinliğin tetiklendiğinden eminim, kullanıcı arayüzüm olduğu gibi tepki veriyor, ancak temsilcim yalnızca son olayı yakalar.

Merak ediyorum:
1. Olayları test etme yöntemim doğru mu?
2. Zincirleme olayları arttırma yöntemim doğru mu?

Yanıtlar:


190

Yaptığınız her şey doğrudur ve testinizin "En son yapılan etkinlik nedir?"

Kodunuz bu iki etkinliği şu sırayla tetikliyor

  • Mülk Değişti (... "Mülküm" ...)
  • Özellik Değişti (... "MyOtherProperty" ...)

Bunun "doğru" olup olmadığı bu olayların amacına bağlıdır.

Yükseltilen etkinliklerin sayısını ve gerçekleşme sırasını test etmek istiyorsanız, mevcut testinizi kolayca genişletebilirsiniz:

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

13
Kısa versiyon: myClass.PropertyChanged + = (nesne gönderen, e) => takenEvents.Add (e.PropertyName);
ShloEmi

22

TDD yapıyorsanız, olay testi çok sayıda tekrarlayan kod üretmeye başlayabilir . Bu durumlar için birim test yazımına çok daha temiz bir yaklaşım sağlayan bir olay monitörü yazdım.

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

Daha fazla ayrıntı için lütfen aşağıdakilere cevabımı inceleyin.

Yansıma kullanarak bir olayın C # ile oluşturulduğunu test eden birim


3
İkinci bağlantı kesildi.
Lennart

10

Bu çok eskidir ve muhtemelen okunmayacaktır, ancak bazı yeni .net özellikleri ile bir INPC Tracer sınıfı oluşturduk:

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

Bkz. Gist: https://gist.github.com/Seikilos/6224204


Güzel - paketlemeyi ve nuget.org'da yayınlamayı düşünmelisiniz
Simon Ejsing

1
Harika iş! Akıcı API'yı gerçekten kazıyorum. Kendime benzer bir şey yaptım ( github.com/f-tischler/EventTesting ), ancak yaklaşımınızın daha özlü olduğunu düşünüyorum.
Florian Tischler

6

Aşağıda, yükseltilmiş olayların sırasını kaydetmek yerine belirli bir olayın kaç kez çağrıldığını sayan biraz değiştirilmiş Andrew'un kodu verilmiştir. Koduna dayanmasına rağmen testlerimde daha kullanışlı buluyorum.

[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

Bu makaleye dayanarak, bu basit iddia yardımcısı oluşturduk:

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

Bu yöntem yardımcısı ile test gerçekten basitleşir.

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

1

Her üye için bir test yazma - bu çok iş

(belki bu çözüm her durum için mükemmel değildir - ancak olası bir yol gösterir. Kullanım durumunuz için uyarlamanız gerekebilir)

Üyelerinizin mülkünüzün değiştiği etkinliğe doğru yanıt verip vermediğini test etmek için kitaplıktaki yansımayı kullanmak mümkündür:

  • Setter erişiminde PropertyChanged olayı oluşturuldu
  • Etkinlik doğru şekilde yükseltildi (mülkün adı yükseltilen etkinliğin bağımsız değişkenine eşit)

Aşağıdaki kod kütüphane olarak kullanılabilir ve aşağıdaki genel sınıfın nasıl test edileceğini gösterir

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

Sınıfınızın testleri artık olarak yazılabilir. (belki testi "etkinlik var" ve "doğru adla oluşturulan etkinlik" e bölmek istersiniz - bunu kendiniz yapabilirsiniz)

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

Sınıf

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

Bunu denedim, ancak özellik ayarlayıcılarımın hepsi benim yaptığım "if (value == _myValue) return" gibi bir koruma ifadesi varsa, bir şey eksik olmadıkça yukarıdaki işe yaramaz. Geçenlerde C ++ C # geldim.
codah

0

Burada bir uzantı yaptım:

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

Kullanımı var:

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

    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.