2012-03-30 9 views
8

通常の解決策は、インターフェイスの背後にそれを隠すことです。 ユニットテストでDateTime.Nowを模擬する方法は?

public class RecordService 
{ 
    private readonly ISystemTime systemTime; 

    public RecordService(ISystemTime systemTime) 
    { 
     this.systemTime = systemTime; 
    } 

    public void RouteRecord(Record record) 
    { 
     if (record.Created < 
      systemTime.CurrentTime().AddMonths(-2)) 
     { 
      // process old record 
     } 

     // process the record 
    } 
} 

ユニットテストでは、モックオブジェクトを使用し、私は現在の時刻を得るために、私のクラスに別のインターフェイスを注入するために好きではない

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     var systemTime = A.Fake<ISystemTime>(); 
     A.CallTo(() => system.Time.CurrentTime()) 
      .Returns(DateTime.Now.AddYears(-1)); 

     var record = new Record(DateTime.Now); 
     var service = new RecordService(systemTime); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

を返すように決めることができます。このような小さな問題のために重すぎる解決策を感じています。解決策は、public関数で静的クラスを使用することです。

public static class SystemTime 
{ 
    public static Func<DateTime> Now =() => DateTime.Now; 
} 

今、私たちはISystemTime注射を削除することができますし、RecordServiceは、我々は同じように簡単にシステム時刻を模擬することができますユニットテストでこの

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     if (record.Created < SystemTime.Now.AddMonths(-2)) 
     { 
      // process old record 
     } 

    // process the record 
    } 
} 

のように見えます。もちろん

[TestClass] 
public class When_old_record_is_processed 
{ 
    [TestMethod] 
    public void Then_it_is_moved_into_old_records_folder() 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(-1); 
     var record = new Record(DateTime.Now); 
     var service = new RecordService(); 

     service.RouteRecord(record); 

     // Asserts... 
    } 
} 

このすべての欠点があります。パブリックフィールド(HORROR!)を使用しているので、誰もこのようなコードを書くことを止めていません。

public class RecordService 
{ 
    public void RouteRecord(Record record) 
    { 
     SystemTime.Now =() => DateTime.Now.AddYears(10); 
    } 
} 

また、開発者を教育するのは、間違いを犯さないように抽象化を作成するよりも優れていると思います。他の可能性のある問題は、テストの実行に関連しています。この機能を元の状態に戻すことを忘れた場合、他のテストに影響する可能性があります。これは単体テストランナがテストを実行する方法によって異なります。 あなたは、公共の機能を使用して(時間、単純なファイルシステム操作をあざける)この種の機能を実装する私の意見ではモックのファイルシステム操作に

public static class FileSystem 
{ 
    public static Action<string, string> MoveFile = File.Move; 
} 

を同じロジックを使用することができます完全に受け入れています。コードを読みやすくし、依存関係を減らし、単体テストを模擬するのは簡単です。

+1

moqを使用しましたか? (またはその他の模擬フレームワーク)http://code.google.com/p/moq/ – Magrangs

+0

コードの定式化を修正しました。それを正しく行う方法については、あなたのバージョンと比較してください。 –

+1

@Magrangs:Moqは、他のすべての「正常な」モッキングフレームワークと同様に、静的メソッドやプロパティを模倣することはできません。「DateTime.Now」。 –

答えて

3

これを手動で実装する必要はありません。 Molesフレームワークを使用してこれを行うことができます。 channel 9

+2

そしてmoq ..と他の多くの模造フレームワーク:-) – Magrangs

+0

実際にこのビデオでは、モルドの最初の例は、DateTime.Nowを模擬するのが簡単な方法です。 – daryal

+1

@Magrangs:正しくありません。上記のあなたの他のコメントへの私の答えを見てください。 –

3

私は現在の時間がとても短期間であったとは言いません。あなたのアプリケーションが国際化され、複数のタイムゾーンで使用されている場合、どのタイムゾーンが現在の時刻を取得していますか?あなたのロケールに関係なく、共通のタイムゾーンを使用できますか?

インターフェイスでは、この知識を抽象化することができます。私は、「現在の」時間をやや複雑な仕事として取り出すことを検討するだろう。

0

私たちは慣用的なC#について話していることを考慮して、インターフェイスの背後にある式の問題をよく理解していません。本質的には、コンストラクタに依存関係として注入するTimeProviderがあり、テスト対象の関連コードのロジックを駆動するためのスタブを提供します。

私はあなたの他のアプローチが利益を提供しているとは思わないし、依存関係注入の有用な側面を排除するだけでなく、慣用的ではないという否定的な側面を持っています(依存関係、嘲笑、

関連する問題