การทดสอบหน่วยว่าเหตุการณ์เพิ่มขึ้นใน C # (ตามลำดับ)


160

ฉันมีรหัสบางอย่างที่ทำให้เกิด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. วิธีการของฉันในการเพิ่มเหตุการณ์ที่ถูกผูกมัด ?

คำตอบ:


190

ทุกสิ่งที่คุณทำถูกต้องหากคุณต้องการให้การทดสอบถามว่า "เหตุการณ์สุดท้ายที่เกิดขึ้นคืออะไร"

รหัสของคุณกำลังยิงเหตุการณ์สองเหตุการณ์นี้ตามลำดับนี้

  • การเปลี่ยนแปลงอสังหาริมทรัพย์ (... "ทรัพย์สินของฉัน" ... )
  • การเปลี่ยนแปลงอสังหาริมทรัพย์ (... "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]);
}

13
เวอร์ชันที่สั้นกว่า: myClass.PropertyChanged + = (ผู้ส่งออบเจ็กต์, e) => receiveEvents.Add (e.PropertyName);
ShloEmi

22

หากคุณกำลังทำ TDD การทดสอบกิจกรรมสามารถเริ่มสร้างรหัสซ้ำจำนวนมากได้ ฉันเขียนการตรวจสอบเหตุการณ์ที่ทำให้วิธีการเขียนหน่วยทดสอบในสถานการณ์เหล่านี้สะอาดขึ้น

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

โปรดดูคำตอบของฉันต่อไปนี้สำหรับรายละเอียดเพิ่มเติม

การทดสอบหน่วยที่เหตุการณ์เพิ่มขึ้นใน C # โดยใช้การสะท้อน


3
ลิงก์ที่สองไม่ทำงาน
Lennart

10

นี่เก่ามากและอาจจะไม่ได้อ่าน แต่ด้วยคุณสมบัติ. 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


สวยงาม - คุณควรพิจารณาบรรจุมันและเผยแพร่บน nuget.org
Simon Ejsing

1
การทำงานที่ดี! ฉันขุด API ที่คล่องแคล่วจริงๆ ฉันได้ทำสิ่งที่คล้ายกันตัวเอง ( github.com/f-tischler/EventTesting ) แต่ฉันคิดว่าวิธีการของคุณกระชับยิ่งขึ้น
Florian Tischler

6

ด้านล่างเป็นรหัสแอนดรูว์ที่เปลี่ยนแปลงเล็กน้อยซึ่งแทนที่จะบันทึกลำดับเหตุการณ์ที่เกิดขึ้นแทนที่จะนับจำนวนครั้งที่มีการเรียกเหตุการณ์เฉพาะ แม้ว่าจะเป็นไปตามรหัสของเขาฉันพบว่ามีประโยชน์มากขึ้นในการทดสอบของฉัน

[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

จากบทความนี้ฉันได้สร้างตัวช่วยยืนยันแบบง่ายนี้:

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

1

อย่าเขียนบททดสอบสำหรับสมาชิกแต่ละคน - นี่เป็นงานที่มาก

(อาจเป็นวิธีการแก้ปัญหานี้ไม่เหมาะสำหรับทุกสถานการณ์ - แต่มันแสดงวิธีที่เป็นไปได้คุณอาจจำเป็นต้องปรับใช้สำหรับกรณีการใช้งานของคุณ)

เป็นไปได้ที่จะใช้การไตร่ตรองในห้องสมุดเพื่อทดสอบว่าสมาชิกของคุณตอบสนองต่อเหตุการณ์ของคุณที่เปลี่ยนแปลงสถานที่ให้บริการอย่างถูกต้องหรือไม่:

  • เหตุการณ์ 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();
        }
    }

การทดสอบในชั้นเรียนของคุณตอนนี้สามารถเขียนเป็น (บางทีคุณต้องการแยกการทดสอบออกเป็น "event is there" และ "event ฟื้นคืนชีพด้วยชื่อที่ถูกต้อง" - คุณสามารถทำได้ด้วยตัวเอง)

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

ฉันลองสิ่งนี้ แต่ถ้าผู้ตั้งค่าคุณสมบัติของฉันมีคำแถลงยามเช่น "if (value == _myValue) return" ซึ่งฉันทำทั้งหมดสิ่งที่กล่าวมาข้างต้นจะไม่ทำงานเว้นแต่ฉันจะทำอะไรหาย ฉันเพิ่งมาจาก C ++ ถึง C #
codah

0

ฉันได้ทำการขยายที่นี่:

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

    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.