Kiểm tra đơn vị rằng các sự kiện được nêu ra trong C # (theo thứ tự)


159

Tôi có một số mã làm tăng PropertyChangedcác sự kiện và tôi muốn có thể kiểm tra đơn vị rằng các sự kiện đang được nêu ra một cách chính xác.

Mã đang nâng cao các sự kiện là như thế

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

Tôi nhận được một bài kiểm tra màu xanh lá cây đẹp từ đoạn mã sau trong bài kiểm tra đơn vị của mình, sử dụng các đại biểu:

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

Tuy nhiên, nếu sau đó tôi thử và xâu chuỗi các thiết lập các thuộc tính với nhau như vậy:

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

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

Thử nghiệm của tôi cho sự kiện không thành công - sự kiện mà nó ghi lại là sự kiện dành cho MyOtherProperty.

Tôi khá chắc chắn rằng sự kiện này sẽ kích hoạt, giao diện người dùng của tôi phản ứng giống như vậy, nhưng đại biểu của tôi chỉ nắm bắt sự kiện cuối cùng để khai hỏa.

Vì vậy, tôi tự hỏi:
1. Phương pháp kiểm tra sự kiện của tôi có đúng không?
2. Phương pháp nâng sự kiện xích của tôi có đúng không?

Câu trả lời:


189

Tất cả mọi thứ bạn đã làm là chính xác, cung cấp cho bạn muốn bài kiểm tra của bạn hỏi "sự kiện cuối cùng được nêu ra là gì?"

Mã của bạn đang bắn hai sự kiện này, theo thứ tự này

  • Tài sản đã thay đổi (... "Tài sản của tôi" ...)
  • Tài sản đã thay đổi (... "MyOtherProperty" ...)

Điều này có "đúng" hay không phụ thuộc vào mục đích của những sự kiện này.

Nếu bạn muốn kiểm tra số lượng sự kiện được nêu ra và thứ tự chúng được nêu ra, bạn có thể dễ dàng mở rộng bài kiểm tra hiện tại của mình:

[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
Phiên bản ngắn hơn: myClass.PropertyChanged + = (người gửi đối tượng, e) => receiveEvents.Add (e.PropertyName);
ShloEmi

22

Nếu bạn đang làm TDD thì kiểm tra sự kiện có thể bắt đầu tạo ra nhiều mã lặp đi lặp lại. Tôi đã viết một trình theo dõi sự kiện cho phép một cách tiếp cận rõ ràng hơn để viết bài kiểm tra đơn vị cho những tình huống này.

var publisher = new PropertyChangedEventPublisher();

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

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

EventMonitor.Assert(test, publisher, expectedSequence);

Xin vui lòng xem câu trả lời của tôi sau đây để biết thêm chi tiết.

Kiểm tra đơn vị rằng một sự kiện được nêu ra trong C #, sử dụng sự phản chiếu


3
Liên kết thứ hai là xuống.
Lennart

10

Điều này rất cũ và có thể sẽ không được đọc nhưng với một số tính năng .net mới thú vị, tôi đã tạo ra một lớp INPC Tracer cho phép:

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

Xem ý chính: https://gist.github.com/Seikilos/6224204


Đẹp - bạn nên xem xét việc đóng gói nó và xuất bản nó trên nuget.org
Simon Ejsing

1
Công việc tuyệt vời Tôi thực sự đào API thông thạo. Tôi đã tự mình làm một cái gì đó tương tự ( github.com/f-tischler/EventTesting ) nhưng tôi nghĩ cách tiếp cận của bạn thậm chí còn ngắn gọn hơn.
Florian Tischler

6

Dưới đây là mã của Andrew thay đổi một chút, thay vì chỉ ghi lại chuỗi các sự kiện được nêu thay vì tính số lần một sự kiện cụ thể đã được gọi. Mặc dù nó dựa trên mã của anh ấy nhưng tôi thấy nó hữu ích hơn trong các thử nghiệm của mình.

[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

Dựa trên bài viết này, tôi đã tạo ra trình trợ giúp khẳng định đơn giản này:

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

Với người trợ giúp phương pháp này, thử nghiệm trở nên thực sự đơn giản.

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

1

Đừng viết bài kiểm tra cho mỗi thành viên - đây là công việc nhiều

(có thể giải pháp này không hoàn hảo cho mọi tình huống - nhưng nó cho thấy một cách có thể. Bạn có thể cần điều chỉnh nó cho trường hợp sử dụng của mình)

Có thể sử dụng sự phản chiếu trong thư viện để kiểm tra xem tất cả các thành viên của bạn có phản ứng với sự kiện thay đổi thuộc tính của bạn một cách chính xác hay không:

  • Sự kiện PropertyChanged được nêu ra khi truy cập setter
  • Sự kiện được nêu lên chính xác (tên của tài sản bằng với đối số của sự kiện đã nêu)

Đoạn mã sau có thể được sử dụng làm thư viện và chỉ ra cách kiểm tra lớp chung sau

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

Các bài kiểm tra của lớp của bạn bây giờ có thể được viết là. (có thể bạn muốn chia bài kiểm tra thành "sự kiện ở đó" và "sự kiện được nêu ra với tên chính xác" - bạn có thể tự làm điều này)

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

Lớp học

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

Tôi đã thử điều này, nhưng nếu setters tài sản của tôi có tuyên bố bảo vệ như "if (value == _myValue) return", tất cả những gì tôi làm, thì những thứ trên sẽ không hoạt động, trừ khi tôi thiếu thứ gì đó. Gần đây tôi đã đến từ C ++ đến C #.
codah

0

Tôi đã thực hiện một phần mở rộng ở đây:

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

Có cách sử dụng:

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

    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.