2010-10-12 38 views
6

Tôi đang sử dụng Reactive Extensions for .NET (Rx) để hiển thị sự kiện dưới dạng IObservable<T>. Tôi muốn tạo một bài kiểm tra đơn vị nơi tôi khẳng định rằng một sự kiện cụ thể được kích hoạt. Đây là phiên bản đơn giản của lớp tôi muốn kiểm tra:Thử nghiệm đơn vị cho một sự kiện sử dụng Tiện ích mở rộng phản ứng

public sealed class ClassUnderTest : IDisposable { 

    Subject<Unit> subject = new Subject<Unit>(); 

    public IObservable<Unit> SomethingHappened { 
    get { return this.subject.AsObservable(); } 
    } 

    public void DoSomething() { 
    this.subject.OnNext(new Unit()); 
    } 

    public void Dispose() { 
    this.subject.OnCompleted(); 
    } 

} 

Rõ ràng các lớp thực của tôi phức tạp hơn. Mục tiêu của tôi là xác minh rằng việc thực hiện một số hành động với lớp đang được kiểm tra dẫn đến một chuỗi các sự kiện được báo hiệu trên IObservable. May mắn là các lớp tôi muốn thử nghiệm thực hiện IDisposable và gọi OnCompleted về chủ đề khi đối tượng được xử lý làm cho nó dễ dàng hơn nhiều để kiểm tra.

Sau đây là cách tôi thử nghiệm:

// Arrange 
var classUnderTest = new ClassUnderTest(); 
var eventFired = false; 
classUnderTest.SomethingHappened.Subscribe(_ => eventFired = true); 

// Act 
classUnderTest.DoSomething(); 

// Assert 
Assert.IsTrue(eventFired); 

Sử dụng một biến để xác định xem một sự kiện là bắn không phải là quá xấu, nhưng trong tình huống phức tạp hơn tôi có thể muốn xác minh rằng một chuỗi sự kiện cụ là Bị sa thải. Có phải là có thể mà không cần ghi lại các sự kiện trong các biến và sau đó thực hiện các xác nhận về các biến? Có thể sử dụng một cú pháp LINQ giống như thông thạo để thực hiện các xác nhận trên IObservable hy vọng sẽ làm cho bài kiểm tra dễ đọc hơn.

+1

BTW, tôi nghĩ rằng có một biến là hoàn toàn tốt. Đoạn mã trên dễ đọc, điều quan trọng nhất. Câu trả lời của @ PL là tốt đẹp và thanh lịch, nhưng bạn phải căng thẳng để hiểu những gì đang xảy ra ... Có thể biến nó thành một phần mở rộng FailIfNothingHappened() –

+0

@Sergey Aldoukhov: Tôi đồng ý, nhưng câu trả lời của PL đã học được cách tôi có thể sử dụng ' Materialize' để lý do về cách hoạt động 'IObservable' của tôi. Và đối với các thử nghiệm phức tạp hơn, sử dụng các biến để nắm bắt những gì đã xảy ra có thể khó hiểu hơn. Ngoài ra, việc tạo một tiện ích mở rộng như bạn đề xuất có thể sẽ giúp bạn hiểu điều gì đang diễn ra dễ dàng hơn. –

+0

Tôi đã chỉnh sửa câu hỏi của mình để làm rõ hơn những gì tôi muốn. –

Trả lời

11

Câu trả lời này đã được cập nhật lên phiên bản 1.0 đã phát hành của Rx.

Tài liệu chính thức vẫn còn nhỏ nhưng Testing and Debugging Observable Sequences trên MSDN là một nơi bắt đầu tốt.

Lớp thử nghiệm sẽ lấy được từ ReactiveTest trong không gian tên Microsoft.Reactive.Testing. Bài kiểm tra dựa trên một số TestScheduler cung cấp thời gian ảo cho bài kiểm tra.

Phương thức TestScheduler.Schedule có thể được sử dụng để xếp hàng hoạt động tại một số điểm (ve) trong thời gian ảo. Thử nghiệm được thực hiện bởi TestScheduler.Start. Điều này sẽ trả về một số ITestableObserver<T> có thể được sử dụng để xác nhận ví dụ bằng cách sử dụng lớp ReactiveAssert.

public class Fixture : ReactiveTest { 

    public void SomethingHappenedTest() { 
    // Arrange 
    var scheduler = new TestScheduler(); 
    var classUnderTest = new ClassUnderTest(); 

    // Act 
    scheduler.Schedule(TimeSpan.FromTicks(20),() => classUnderTest.DoSomething()); 
    var actual = scheduler.Start(
    () => classUnderTest.SomethingHappened, 
     created: 0, 
     subscribed: 10, 
     disposed: 100 
    ); 

    // Assert 
    var expected = new[] { OnNext(20, new Unit()) }; 
    ReactiveAssert.AreElementsEqual(expected, actual.Messages); 
    } 

} 

TestScheduler.Schedule được sử dụng để sắp xếp một cuộc gọi đến DoSomething lúc 20 (đo bằng ve).

Sau đó, TestScheduler.Start được sử dụng để thực hiện kiểm tra thực tế trên quan sát SomethingHappened. Tuổi thọ của thuê bao được kiểm soát bởi các đối số cho cuộc gọi (một lần nữa được đo bằng bọ ve).

Cuối cùng ReactiveAssert.AreElementsEqual được sử dụng để xác minh rằng OnNext được gọi vào thời điểm 20 như mong đợi.

Kiểm tra xác minh rằng gọi DoSomething sẽ ngay lập tức kích hoạt số SomethingHappened quan sát được.

0

Không chắc chắn về thông thạo hơn nhưng điều này sẽ thực hiện thủ thuật mà không đưa ra biến số.

var subject = new Subject<Unit>(); 
subject 
    .AsObservable() 
    .Materialize() 
    .Take(1) 
    .Where(n => n.Kind == NotificationKind.OnCompleted) 
    .Subscribe(_ => Assert.Fail()); 

subject.OnNext(new Unit()); 
subject.OnCompleted(); 
+1

Tôi khá chắc chắn điều này sẽ không làm việc, khẳng định trong một Đăng ký thường dẫn đến những điều kỳ lạ xảy ra và thử nghiệm vẫn đi qua. –

+0

Để kiểm tra không thành công, bạn nên bình luận dòng với lệnh gọi OnNext. –

+1

Chỉ một điểm; AsObservable() không phục vụ mục đích nào trong ví dụ này. –

3

Loại thử nghiệm này cho các quan sát sẽ không đầy đủ. Mới đây, nhóm RX đã xuất bản bộ kiểm tra lịch biểu và một số phần mở rộng (mà BTW họ sử dụng trong nội bộ để kiểm tra thư viện). Sử dụng những điều này, bạn không chỉ có thể kiểm tra xem có điều gì xảy ra hay không, mà còn đảm bảo rằng thời gian và đơn đặt hàng và đặt hàng là chính xác. Là phần thưởng, trình kiểm tra lịch biểu cho phép bạn chạy thử nghiệm của mình trong "thời gian ảo", vì vậy các kiểm tra sẽ chạy ngay lập tức, bất kể bạn đang sử dụng chậm trễ đến mức nào.

Jeffrey van Gogh từ đội RX published an article on how to do such kind of testing.

Các thử nghiệm trên, sử dụng phương pháp đã đề cập, sẽ giống như thế này:

[TestMethod] 
    public void SimpleTest() 
    { 
     var sched = new TestScheduler(); 
     var subject = new Subject<Unit>(); 
     var observable = subject.AsObservable(); 

     var o = sched.CreateHotObservable(
      OnNext(210, new Unit()) 
      ,OnCompleted<Unit>(250) 
      ); 
     var results = sched.Run(() => 
            { 
             o.Subscribe(subject); 
             return observable; 
            }); 
     results.AssertEqual(
      OnNext(210, new Unit()) 
      ,OnCompleted<Unit>(250) 
      ); 
    }: 

EDIT: Bạn cũng có thể gọi .OnNext (hoặc một số phương pháp khác) ngầm:

 var o = sched.CreateHotObservable(OnNext(210, new Unit())); 
     var results = sched.Run(() => 
     { 
      o.Subscribe(_ => subject.OnNext(new Unit())); 
      return observable; 
     }); 
     results.AssertEqual(OnNext(210, new Unit())); 

Quan điểm của tôi là - trong những trường hợp đơn giản nhất, bạn chỉ cần đảm bảo sự kiện được kích hoạt (bạn đang kiểm tra Nơi làm việc của bạn orrectly). Nhưng sau đó khi bạn tiến bộ trong sự phức tạp, bạn bắt đầu thử nghiệm cho thời gian, hoặc hoàn thành, hoặc cái gì khác mà đòi hỏi phải có lịch trình ảo. Nhưng bản chất của thử nghiệm bằng cách sử dụng bộ lập lịch ảo, ngược lại với các thử nghiệm "bình thường" là kiểm tra toàn bộ quan sát cùng một lúc, chứ không phải là các hoạt động "nguyên tử".

Vì vậy, có thể bạn sẽ phải chuyển sang bộ lập lịch ảo ở đâu đó trên đường - tại sao không bắt đầu với nó ngay từ đầu?

P.S. Ngoài ra, bạn sẽ phải sử dụng một logic khác cho từng trường hợp thử nghiệm - f.e. bạn sẽ có thể quan sát rất khác nhau để kiểm tra rằng có điều gì đó không xảy ra đối diện với điều gì đó đã xảy ra.

+2

Cách tiếp cận này có vẻ rất phù hợp để kiểm tra một thứ gì đó giống như bản thân Rx. Tuy nhiên, tôi không muốn tổng hợp các cuộc gọi 'OnNext'. Thay vào đó, tôi muốn khẳng định rằng một lời gọi đến phương thức trong lớp mà tôi đang thử nghiệm thực tế dẫn đến một cuộc gọi đến 'OnNext' trên một' IObservable'. –

Các vấn đề liên quan