2011-01-04 2 views
3

土曜日または日曜日に特定の機能を実行しないビジネスロジックがあります。私は単体テストでこれらの機能が実行されていることを確認したいが、土曜日/日曜日にテストは失敗する。ユニットテストでDateTime.Nowを処理するための戦略

土曜日/日曜日に実行されたテスト結果が有効でないことを伝える簡単なメッセージを単体テストに伝えるのが最も簡単な方法です。 C#の/ NUnitのでは

...

Assert.That(
    DateTime.Now.DayOfWeek != DayOfWeek.Sunday && DateTime.Now.DayOfWeek !=  
    DayOfWeek.Saturday, "This test cannot be run on Saturday or Sunday"); 

はそれが日付を試してみて、模擬することをお勧め/ことは可能ですか?このシナリオを処理するための他の戦略はありますか?

答えて

8

日付を試してみるとよいでしょうか?

はい、それだけでなく、単体でコードを確実に単体テストする唯一の方法です。それは、静的メソッド(より正確には、静的Nowプロパティ)に依存しているので、それはあなたがそれをテストすることはできませんことは明らか以上だ

public bool Foo() 
{ 
    return (DateTime.Now.DayOfWeek == DayOfWeek.Sunday); 
} 

:方法あなたは、以下の(無意味)をテストしたいと仮定します。このようにしっかりと結合されたコンポーネントは、単体でテストすることはできません。

今、この改善(コンストラクタDIとの懸念の分離を)考えてみます。

private readonly Func<DateTime> _nowProvider; 
public SomeClass(Func<DateTime> nowProvider) 
{ 
    _nowProvider = nowProvider; 
} 

public bool Foo() 
{ 
    return (_nowProvider().DayOfWeek == DayOfWeek.Sunday); 
} 

今では、ユニットテストにはるかに優れかつ簡単です。 SomeClassは、非決定的な日付に依存しなくなりました。実際のアプリケーションでは、明らかにこのようなSomeClassをインスタンス化します:あなたは上の抽象化を作成する必要があります

var subjectUnderTest = new SomeClass(() => new DateTime(2011, 1, 3)); 
var actual = subjectUnderTest.Foo(); 
// assertions, ... 
+1

私は数ヶ月前にこれを頼んだことを祈っています。ありがとう! :) – Mayo

+0

これは、インターフェイスがIoCで制御されている場合と比較して簡単な方法です。しかし、あなたが望むのは、現在の日時を模擬することだけだと思う​​なら、私は素晴らしいと思います。 –

1

var s = new SomeClass(() => DateTime.Now); 
s.Foo(); 

とあなたのユニットテストであなたは両方のケースを検証することができるようにあなたがそれを模擬します

RTM unit testsを書くことができるように、他のシステムリソースと外部リソースの抽象概念を作成する必要があるため、システムクロックを使用する必要があります。

システムクロック以上の抽象化は非常に簡単で、次のようになります。システムクロックに依存して、アプリケーション内の

public interface ISystemClock 
{ 
    DateTime Now { get; } 
} 

クラスは、より一般的に、コンストラクタの引数としてISystemClockを持っている必要があります。今、あなたはこのようなあなたのユニットテストでは、この偽のクロックを使用することができます

public class FakeSystemClock : ISystemClock 
{ 
    // Note the setter. 
    public DateTime Now { get; set; } 
} 

:あなたが定義することができ、アプリケーションのプロジェクトで

[TestMethod] 
[ExpectedException(typeof(InvalidOperationException), 
    "Service should throw an exception when called on saturday.")] 
public void DoSomething_WhenCalledOnSaturday_ThrowsException() 
{ 
    // Arrange 
    var saturday = new DateTime(2011, 1, 1); 
    Assert.AreEqual(saturday.DayOfWeek, DayOfWeek.Saturday, 
     "Test setup fail"); 
    var clock = new FakeSystemClock() { Now = saturday }; 
    var service = new Service(clock); 

    // Act 
    service.DoSomething(); 
} 

ユニットテストシナリオのあなたは、このようになりますISystemClock実装を使用することができますについて実際にシステムクロックを使用する実装であるISystemClockです。与えられたISystemClock定義を使用して、それは次のようになります。

public class RealSystemClock : ISystemClock 
{ 
    public DateTime Now 
    { 
     get { return DateTime.Now; } 
    } 
} 

あなたはDIコンテナを使用すると、あなたのタイプを配線して自動的にコンストラクタはISystemClock引数を指定した場合RealSystemClockのインスタンスを注入するように設定することができます。

btw。 Func<DateTime>デリゲートを使用してトリックを行うことができますが、このためのインターフェイスを定義することは他の開発者にとってははるかに明白であり、はるかに読みやすくなります。それ以外にもDIコンテナ内のすべてを配線する方がはるかに簡単です。複数のFunc<DateTime>の依存関係がすべて「現在の現地時間を与えてください」とは異なることになります。

こちらがお役に立てば幸いです。

+0

また、 'ISystemClock'のこれらの代替名を考えてみましょう:' ITimeProvider'、 'IClock'。また、フレームワークデザインガイドラインに従うとき、 'ISystemClock.Now'プロパティはメソッドでなければならず、おそらく' GetCurrentTime() 'または' GetCurrentLocalTime() 'と呼ばれます(実際には' DateTime.Now'はaメソッドであり、プロパティではありません)。 – Steven

関連する問題