Pengujian unit bahwa peristiwa dimunculkan dalam C # (berurutan)


160

Saya memiliki beberapa kode yang memunculkan PropertyChangedperistiwa dan saya ingin dapat menguji unit bahwa peristiwa tersebut dimunculkan dengan benar.

Kode yang meningkatkan acara adalah seperti

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

Saya mendapatkan tes hijau yang bagus dari kode berikut dalam unit test saya, yang menggunakan delegasi:

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

Namun, jika saya kemudian mencoba dan rangkaian pengaturan properti bersama seperti:

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

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

Tes saya untuk acara tersebut gagal - peristiwa yang ditangkapnya adalah acara untuk MyOtherProperty.

Saya cukup yakin acara ini menyala, UI saya bereaksi seperti itu, tetapi delegasi saya hanya menangkap acara terakhir yang akan dipecat.

Jadi saya bertanya-tanya:
1. Apakah metode pengujian peristiwa saya benar?
2. Apakah metode saya meningkatkan acara berantai benar?

Jawaban:


190

Semua yang Anda lakukan adalah benar, asalkan Anda ingin ujian Anda bertanya, "Apa acara terakhir yang dimunculkan?"

Kode Anda menjalankan dua peristiwa ini, dalam urutan ini

  • Properti Berubah (... "Properti Saya" ...)
  • Properti Berubah (... "MyOtherProperty" ...)

Apakah ini "benar" atau tidak tergantung pada tujuan dari peristiwa ini.

Jika Anda ingin menguji jumlah acara yang dibesarkan, dan urutan kenaikannya, Anda dapat dengan mudah memperpanjang tes yang ada:

[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
Versi lebih pendek: myClass.PropertyChanged + = (pengirim objek, e) => acceptEvents.Add (e.PropertyName);
ShloEmi

22

Jika Anda melakukan TDD maka pengujian peristiwa dapat mulai menghasilkan banyak kode berulang. Saya menulis monitor acara yang memungkinkan pendekatan yang jauh lebih bersih untuk penulisan tes unit untuk situasi ini.

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

Silakan lihat jawaban saya untuk yang berikut untuk lebih jelasnya.

Unit menguji bahwa suatu peristiwa dimunculkan dalam C #, menggunakan refleksi


3
Tautan kedua rusak.
Lennart

10

Ini sangat tua dan mungkin tidak akan dibaca tetapi dengan beberapa fitur .net baru yang keren Saya telah membuat kelas INPC Tracer yang memungkinkan:

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

Lihat inti: https://gist.github.com/Seikilos/6224204


Cantik - Anda harus mempertimbangkan untuk mengemasnya dan menerbitkannya di nuget.org
Simon Ejsing

1
Kerja bagus! Saya benar-benar menggali API yang lancar. Saya sendiri telah melakukan sesuatu yang serupa ( github.com/f-tischler/EventTesting ) tetapi saya pikir pendekatan Anda bahkan lebih ringkas.
Florian Tischler

6

Di bawah ini adalah kode Andrew yang sedikit berubah yang alih-alih hanya mencatat urutan peristiwa yang dibangkitkan, melainkan menghitung berapa kali peristiwa tertentu dipanggil. Meskipun didasarkan pada kodenya saya merasa lebih berguna dalam pengujian saya.

[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

Berdasarkan artikel ini, saya telah membuat pembantu pernyataan sederhana ini:

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

Dengan metode penolong ini, tes menjadi sangat sederhana.

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

1

Jangan menulis tes untuk setiap anggota - ini banyak pekerjaan

(mungkin solusi ini tidak sempurna untuk setiap situasi - tetapi ini menunjukkan cara yang mungkin. Anda mungkin perlu menyesuaikannya untuk kasus penggunaan Anda)

Dimungkinkan untuk menggunakan refleksi di perpustakaan untuk menguji apakah anggota Anda semua merespons acara Anda yang diubah properti dengan benar:

  • Acara PropertyChanged dimunculkan pada akses setter
  • Acara dimunculkan dengan benar (nama properti sama dengan argumen dari acara yang dimunculkan)

Kode berikut dapat digunakan sebagai perpustakaan dan menunjukkan cara menguji kelas generik berikut

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

Tes kelas Anda sekarang dapat ditulis sebagai. (mungkin Anda ingin membagi tes menjadi "acara di sana" dan "acara yang diangkat dengan nama yang benar" - Anda dapat melakukannya sendiri)

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

Kelas

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

Saya mencoba ini, tetapi jika seter properti saya memiliki pernyataan penjaga seperti "if (value == _myValue) return", yang semua milik saya lakukan, maka hal di atas tidak akan berfungsi, kecuali saya kehilangan sesuatu. Saya baru saja datang dari C ++ ke C #.
codah

0

Saya telah membuat ekstensi di sini:

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

Ada penggunaannya:

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

    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.