2013-06-05 16 views
5

Şu anda zamanlayıcılar kavramını soyutlamaktayım ki, ihtiyaç duyan sınıflarım, test modunda ya da farklı modda operasyon modlarında (örneğin, threadpool zamanlayıcıları, thread-affine zamanlayıcıları, vb.) Sahte zamanlayıcılar kullanabilirler.Bir birim sınıfı nasıl test edilir (adaptör modeli)?

public interface ITimer : IDisposable 
{ 
    bool IsEnabled { get; } 
    bool IsAutoResetting { get; set; } 
    TimeSpan Interval { get; set; } 

    void Start(); 
    void Stop(); 

    event EventHandler IntervalElapsed; 
} 

Şimdi System.Threading.Timer sınıf uyum sağlar ve bu arabirimi uygulayan bir sarmalayıcı oluşturmak istiyorum: Bu nedenle, bu arayüzü yarattı. Test odaklı geliştirmeyi kullanarak yapmak istiyorum. Benim sınıf anda biraz şuna benzer:

public sealed class ThreadPoolTimer : ITimer 
{ 
    private readonly Timer _timer; 

    public bool IsEnabled { get; private set; } 

    public bool IsAutoResetting { get; set; } 

    public TimeSpan Interval { get; set; } 

    public ThreadPoolTimer() 
    { 
     Interval = this.GetDefaultInterval(); 
     _timer = new Timer(OnTimerCallback); 
    } 

    public void Dispose() 
    { 
     _timer.Dispose(); 
    } 

    public void Start() 
    { 

    } 

    public void Stop() 
    { 

    } 

    private void OnTimerCallback(object state) 
    { 
     OnIntervalElapsed(); 
    } 

    public event EventHandler IntervalElapsed; 

    private void OnIntervalElapsed() 
    { 
     var handler = IntervalElapsed; 
     if (handler != null) 
      handler(this, EventArgs.Empty); 
    } 
} 

Benim asıl soru şudur: nasıl Start, Stop ve IntervalElapsed davranışlarından (yumuşak gerçek zamanlı) gereksinimlerini tanımlayan birim testleri yazardınız?

Kanaatimce, örn. Bir AutoResetEvent ve olay belirli bir zaman dilimi içinde yükseltilmiş olup olmadığını kontrol edin (belki +/- 3ms). Ancak bu kodu yazmak DAMP (betimsel ve anlamlı ifadeler) ilkesini biraz ihlal ediyor, bence. Bunu yapmanın daha kolay bir yolu var mı?

Bağımlılığı System.Threading.Timer harici yapmalıyım ve daha sonra test amacıyla bir shim kullanabilir miyim? Ne yazık ki, .NET zamanlayıcıları ortak bir arabirime sahip değil (ki bu benim işimi eskimiş olur) ...

Bu konuyla ilgili düşünceleriniz nelerdir? Henüz bulamadığım ve okumam gereken belgeler var mı?

Aslında bu gönderide birden fazla soruya sahip olduğum için üzgünüm, ancak bu gerçek zamanlı gereksinimlerin test edilmesi oldukça ilginç, bence.

+0

olası kopyalama http://stackoverflow.com/questions/9088313/unit-testing-system-timers-timer – Mzf

+2

bir yineleme çünkü tüm bu sorular bir zamanlayıcıyı kapsülleyen sınıflara ve bununla nasıl başa çıkılacağına, ancak TDD'yi kullanarak gerçek arabirimi uygulayan hiçbir şeye başvurmaz. Bununla ilgili diğer sorular örn. http: // stackoverflow.com/questions/879971/how-do-you-unit-test-classes-bu-kullanım-zamanlayıcılar-dahili veya http://stackoverflow.com/questions/12045/unit-testing-a-timer-based-application – feO2x

+0

bir sarıcı oluşturmak bir koku gibi hissediyor. Çerçevedeki zamanlayıcı sınıfının çalışmasının garanti edildiğini düşünüyorum. Zamanlayıcılarımı çağırmak için konuyu test etmeyi veya birkaç entegrasyon testi yazmak için kendi aralıklarımı enjekte etmeyi seçerdim. – bryanbcook

cevap

0

Hiç kimse bu soruyu henüz yanıtlamadı, size sorunu nasıl çözdüğümü söyleyeceğim: Zamanlayıcı davranışını gözlemleyen kodu gerçekte uygulamak için casus modelini kullandım. sınıf şuna benzer:

public class ThreadPoolTimerSpy : IDisposable 
{ 
    private readonly ThreadPoolTimer _threadPoolTimer; 

    private int _intervalElapsedCallCount; 

    private readonly ManualResetEvent _resetEvent = new ManualResetEvent(false); 

    public int NumberOfIntervals { get; set; } 

    public DateTime StartTime { get; private set; } 
    public DateTime EndTime { get; private set; } 

    public ThreadPoolTimerSpy(ThreadPoolTimer threadPoolTimer) 
    { 
     if (threadPoolTimer == null) throw new ArgumentNullException("threadPoolTimer"); 
     _threadPoolTimer = threadPoolTimer; 
     _threadPoolTimer.IntervalElapsed += OnIntervalElapsed; 
     NumberOfIntervals = 1; 
    } 

    public void Measure() 
    { 
     _intervalElapsedCallCount = 0; 
     _resetEvent.Reset(); 
     StartTime = DateTime.Now; 
     _threadPoolTimer.Start(); 

     _resetEvent.WaitOne(); 
    } 

    private void OnIntervalElapsed(object sender, EventArgs arguments) 
    { 
     _intervalElapsedCallCount++; 

     if (_intervalElapsedCallCount < NumberOfIntervals) 
      return; 

     _threadPoolTimer.Stop(); 
     EndTime = DateTime.Now; 
     _resetEvent.Set(); 
    } 


    public void Dispose() 
    { 
     _threadPoolTimer.Dispose(); 
     _resetEvent.Dispose(); 
    } 
} 

Bu sınıf bir ThreadPoolTimer alır ve onun IntervalElapsed olaya kaydeder. Biri, casusun ölçmeyi durdurana kadar ne kadar süre beklemesi gerektiğini belirleyebilir. ManualResetEvent kullanırken, Measure yönteminde zamanlayıcıyı başlatan iş parçacığı engellemek için, bu yönteme yapılan tüm çağrılar senkronize edilir, bu da benim görüşüme göre gerçek sınama sınıfında DAMP koduyla sonuçlanır.

şu şekilde görünecektir casus kullanan bir test yöntemi: Herhangi bir sorunuz veya öneriler varsa

[TestInitialize] 
public void InitializeTestEnvironment() 
{ 
    _testTarget = new ThreadPoolTimerBuilder().WithAutoResetOption(true) 
               .WithInterval(100) 
               .Build() as ThreadPoolTimer; 
    Assert.IsNotNull(_testTarget); 
    _spy = new ThreadPoolTimerSpy(_testTarget); 
} 

[TestMethod] 
public void IntervalElapsedMustBeRaisedExactlyTenTimesAfter1000Milliseconds() 
{ 
    CheckIntervalElapsed(10, TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(100)); 
} 

private void CheckIntervalElapsed(int numberOfIntervals, TimeSpan expectedTime, TimeSpan toleranceInterval) 
{ 
    _spy.NumberOfIntervals = numberOfIntervals; 
    _spy.Measure(); 
    var passedTime = _spy.EndTime - _spy.StartTime; 
    var timingDifference = Math.Abs(expectedTime.Milliseconds - passedTime.Milliseconds); 
    Assert.IsTrue(timingDifference <= toleranceInterval.Milliseconds, string.Format("Timing difference: {0}", timingDifference)); 
} 

, Yorum bırakmak için çekinmeyin. Ayrıca, test yapmak için seçmem gereken tolerans aralığı nispeten yüksektir. Belki 3 ila 5 milisaniyenin yeterli olabileceğini düşündüm, ama sonunda on aralık için gerçek ölçülen zaman aralığının bu durumda beklenen 1000 ms süresinden 72 ms daha fazla olduğunu buldum. Peki, gerçek zamanlı uygulamalar için yönetilen bir çalışma zamanı kullanmayın, sanırım ...