テスト中にDateTime.Nowを上書きする良い方法は何ですか?


116

今日の日付に基づいて将来の計算を正しく行う(C#)コードがあります。テストで今日の日付を使用すると、テストで計算を繰り返す必要がありますが、これは正しくありません。結果が既知の値であることをテストできるように、テスト内で日付を既知の値に設定する最良の方法は何ですか?

回答:


157

私の好みは、時間を使用するクラスが実際にインターフェースに依存していることです

interface IClock
{
    DateTime Now { get; } 
}

具体的な実装で

class SystemClock: IClock
{
     DateTime Now { get { return DateTime.Now; } }
}

次に、必要に応じて、テストに必要な他の種類のクロックを提供できます。

class StaticClock: IClock
{
     DateTime Now { get { return new DateTime(2008, 09, 3, 9, 6, 13); } }
}

それに依存するクラスにクロックを提供する際にオーバーヘッドが発生する可能性がありますが、これは、任意の数の依存性注入ソリューション(Inversion of Controlコンテナー、プレーンな古いコンストラクター/セッター注入、または静的ゲートウェイパターンを使用することでも処理できます))。

希望する時間を提供するオブジェクトまたはメソッドを配信する他のメカニズムも機能しますが、重要なことは、システムクロックのリセットを回避することです。

また、それを使用DateTime.Nowして計算に含めることは、適切に感じられないだけではありません。たとえば、深夜の境界付近や火曜日にのみ発生するバグを発見した場合など、特定の時間をテストする機能を奪います。現在の時刻を使用しても、これらのシナリオをテストすることはできません。または、少なくともいつでも好きなときに。


2
これをxUnit.net拡張機能の1つで実際に形式化しました。DateTimeではなくstaticとして使用するClockクラスがあり、特定の日付を含めて、クロックを「フリーズ」および「フリーズ」できます。is.gd/3xdsおよびis.gd/3xduを
ブラッドウィルソン

2
また、システムクロックの方法を置き換える場合、たとえば、分散しているタイムゾーンに支店がある企業でグローバルクロックを使用している場合に、この方法を使用すると、ビジネスレベルで自由に変更できることにも注意してください。 「今」の意味。
Mike Burton、

1
この方法は、依存性注入フレームワークを使用してIClockインスタンスに到達すると共に、私にとって非常にうまく機能します。
Wilka、2008年

8
すばらしい答えです。ほとんどすべての場合UtcNowに使用し、コードの懸念事項(ビジネスロジック、UIなど)に応じて適切に調整する必要があることを追加したいだけです。タイムゾーン全体でのDateTime操作は地雷原ですが、最初の最初の足は常にUTC時間。
アダムラルフ

@BradWilsonこれらのリンクは現在壊れています。WayBackでそれらを引き上げることもできませんでした。

55

Ayende Rahien 、かなり単純な静的メソッドを使用しています...

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

1
スタブ/モックがパブリックグローバル変数(クラス静的変数)を指すようにすることは危険に思われます。テスト対象のシステムのみにスコープを設定する方がよいでしょうか。たとえば、テスト対象のクラスのプライベート静的メンバーにするなどです。
アーロン

1
それはスタイルの問題です。これは、単体テストで変更可能なシステム時間を取得するためにできる最も簡単なことです。
Anthony Mastrean

3
私見、グローバルスタティックシングルトンの代わりにインターフェイスを使用することをお勧めします。次のシナリオを考えてみます。テストランナーは効率的で、できるだけ多くのテストを並行して実行しています。1つのテストは特定の時間をXに変更し、もう1つはYに変更します。これで競合が発生し、これら2つのテストは失敗を切り替えます。インターフェースを使用する場合、各テストは彼のニーズに応じてインターフェースを模倣し、各テストは他のテストから分離されます。HTH。
ShloEmi 2015

17

現在の日付を取得するような単純なもののために別の時計クラスを作成することは少しやり過ぎだと思います。

テストで別の日付を入力できるように、今日の日付をパラメーターとして渡すことができます。これには、コードをより柔軟にするという追加の利点があります。


私はあなたとブレアの答えの両方を+1しましたが、どちらも反対です。どちらのアプローチも有効だと思います。あなたのアプローチは、おそらくUnityのようなものを使用しないプロジェクトを使用するでしょう。
RichardOD 2009年

1
はい、「今」のパラメータを追加することは可能です。ただし、場合によっては、通常は公開したくないパラメーターを公開する必要があります。たとえば、日付から現在までの日数を計算するメソッドがあり、「今」をパラメータとして公開したくない場合は、結果を操作できるためです。それが独自のコードである場合は、パラメータとして「今」を追加してください。ただし、チームで作業している場合は、他の開発者がコードを何に使用するかわからないため、「今」がコードの重要な部分である場合は、操作や誤用から保護します。
Stitch10925 2017

17

Microsoft Fakesを使用してシムを作成することは、これを行う非常に簡単な方法です。次のクラスがあるとします。

public class MyClass
{
    public string WhatsTheTime()
    {
        return DateTime.Now.ToString();
    }

}

Visual Studio 2012では、Fakes / Shimを作成するアセンブリを右クリックして[Fakesアセンブリの追加]を選択することにより、Fakesアセンブリをテストプロジェクトに追加できます。

フェイクアセンブリの追加

最後に、テストクラスは次のようになります。

using System;
using ConsoleApplication11;
using Microsoft.QualityTools.Testing.Fakes;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace DateTimeTest
{
[TestClass]
public class UnitTest1
{
    [TestMethod]
    public void TestWhatsTheTime()
    {

        using(ShimsContext.Create()){

            //Arrange
            System.Fakes.ShimDateTime.NowGet =
            () =>
            { return new DateTime(2010, 1, 1); };

            var myClass = new MyClass();

            //Act
            var timeString = myClass.WhatsTheTime();

            //Assert
            Assert.AreEqual("1/1/2010 12:00:00 AM",timeString);

        }
    }
}
}

1
これはまさに私が探していたものでした。ありがとう!ところで、VS 2013年に同じように動作します
ダグラスラドロー

または最近、VS 2015 Enterpriseエディション。これは、そのようなベストプラクティスとしては残念です。
RJB 2016

12

単体テストを成功させる鍵は、分離です。興味のあるコードを外部の依存関係から分離する必要があるので、それを個別にテストできます。(幸いにも、テスト駆動開発は分離されたコードを生成します。)

この場合、外部は現在のDateTimeです。

ここでのアドバイスは、DateTimeを処理するロジックを新しいメソッドやクラス、またはケースで意味のあるものに抽出し、DateTimeを渡すことです。これで、単体テストで任意のDateTimeを渡して、予測可能な結果を​​生成できます。


10

Microsoft Moles(.NETの分離フレームワーク)を使用するもう1つ。

MDateTime.NowGet = () => new DateTime(2000, 1, 1);

Molesでは、任意の.NETメソッドをデリゲートに置き換えることができます。Molesは静的メソッドまたは非仮想メソッドをサポートしています。MolesはPexのプロファイラーに依存しています。


これは美しいですが、Visual Studio 2010が必要です。:-(
Pandincus

VS 2008でも同様に機能します。ただし、MSTestで最適に動作します。NUnitを使用できますが、その場合、特別なテストランナーを使用してテストを実行する必要があると思います。
Torbjørn

私は可能な限りMoles(別名Microsoft Fakes)の使用を避けます。理想的には、依存関係の注入によってまだテストできないレガシーコードにのみ使用する必要があります。
brianpeiris 2013年

1
@ brianpeiris、Microsoft Fakesを使用することの欠点は何ですか?
レイチェン2013

サードパーティのコンテンツを常にDIにすることはできないため、ユニットテストでインスタンス化したくないサードパーティのAPIをコードが呼び出す場合は、Fakesでユニットテストを完全に受け入れることができます。私は自分のコードに偽物/モールを使用しないことに同意しますが、それは他の目的には完全に受け入れられます。
ChrisCW 2016年



2

テストするクラスで使用するクラス(better:method / delegate)を注入できますDateTime.Now。ているDateTime.Nowデフォルト値とは、唯一のダミーメソッドを返すことに一定値にテストして、それを設定します。

編集:ブレア・コンラッドが言ったこと(彼にはいくつかのコードがあります)。例外として、私はデリゲートを好む傾向があります。デリゲートはクラス階層を次のようなもので乱雑にしないためIClockです...


1

私はこの状況に頻繁に直面したため、インターフェイスを介してNowプロパティを公​​開する単純なnugetを作成しました。

public interface IDateTimeTools
{
    DateTime Now { get; }
}

もちろん実装は非常に簡単です

public class DateTimeTools : IDateTimeTools
{
    public DateTime Now => DateTime.Now;
}

したがって、プロジェクトにnugetを追加した後、単体テストでそれを使用できます

ここに画像の説明を入力してください

GUI Nuget Package Managerから直接、または次のコマンドを使用してモジュールをインストールできます。

Install-Package -Id DateTimePT -ProjectName Project

そしてNugetのコードはこちらです。

Autofacでの使用例はここにあります


-11

条件付きコンパイルを使用して、デバッグ/展開中に発生することを制御することを検討しましたか?

例えば

DateTime date;
#if DEBUG
  date = new DateTime(2008, 09, 04);
#else
  date = DateTime.Now;
#endif

それができない場合は、プロパティを公開して操作できるようにします。これはすべて、テスト可能なコードを書くという課題の一部です。これは、現在私が取り組んでいることです:D

編集する

私の大部分はブレアのアプローチを好みます。これにより、コードの一部を「ホットプラグ」してテストを支援できます。それはすべて、設計原則に従っており、変化するものをカプセル化しますコードテストコードをし、本番コードと同じであり、外部からは誰も見ないだけです。

ただし、この例では、作成とインターフェイスの作成は大変な作業のように思えるかもしれません(そのため、条件付きコンパイルを選択しました)。


わあ、あなたはこの答えに強く当たった。これは私が交換する方法を呼び出す単一の場所にあるが、何をしてきたあるDateTimeのをNowしてTodayなど
デイブCousineau
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.