テスト中に DateTime.Now を上書きする良い方法は何ですか?
-
09-06-2019 - |
質問
今日の日付に基づいて将来のことを正しく計算する (C#) コードがいくつかあります。テストで今日の日付を使用すると、テストで計算をやり直す必要があり、気分が悪くなります。結果が既知の値であることをテストできるように、テスト内で日付を既知の値に設定する最良の方法は何ですか?
解決
私の好みは、時間を使用するクラスが実際にインターフェイスに依存するようにすることです。
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); } }
}
クロックに依存するクラスにクロックを提供する際にオーバーヘッドが発生する可能性がありますが、それは任意の数の依存性注入ソリューション (制御の反転コンテナー、単純な古いコンストラクター/セッター注入、または 静的ゲートウェイ パターン).
必要な時間を提供するオブジェクトやメソッドを提供する他のメカニズムも機能しますが、重要なのは、システム クロックのリセットを回避することだと思います。これは、他のレベルで問題が発生するだけだからです。
また、使用して DateTime.Now
それを計算に含めることは、単に正しくないと感じるだけではありません。たとえば、深夜の境界付近や火曜日にのみ発生するバグを発見した場合など、特定の時間をテストする能力が奪われてしまいます。現在の時間を使用すると、これらのシナリオをテストできません。少なくとも、いつでも好きなときにはできません。
他のヒント
アイエンデ・ラヒエン 用途 かなり単純な静的メソッド...
public static class SystemTime
{
public static Func<DateTime> Now = () => DateTime.Now;
}
現在の日付を取得するなどの単純なことのために別の時計クラスを作成するのは少しやりすぎだと思います。
今日の日付をパラメータとして渡すことで、テストに別の日付を入力できます。これには、コードがより柔軟になるという追加の利点があります。
Microsoft Fakes を使用して shim を作成するのは、非常に簡単な方法です。次のクラスがあるとします。
public class MyClass
{
public string WhatsTheTime()
{
return DateTime.Now.ToString();
}
}
Visual Studio 2012 では、Fakes/Shim を作成したいアセンブリを右クリックし、[Add Fakes Assembly] を選択することで、テスト プロジェクトに 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);
}
}
}
}
単体テストを成功させる鍵は、 デカップリング. 。興味深いコードを外部の依存関係から分離して、単独でテストできるようにする必要があります。(幸いなことに、テスト駆動開発では分離されたコードが生成されます。)
この場合、外部は現在の DateTime です。
ここでの私のアドバイスは、DateTime を処理するロジックを新しいメソッドやクラスなど、あなたの場合に意味のあるものに抽出し、DateTime を渡すことです。これで、単体テストで任意の DateTime を渡して、予測可能な結果を生成できるようになりました。
Microsoft Moles を使用した別の例 (.NET の分離フレームワーク).
MDateTime.NowGet = () => new DateTime(2000, 1, 1);
Molesでは、任意の.NETメソッドをデリゲートに置き換えることができます。Molesは、静的または非仮想的な方法をサポートします。Molesは、Pexのプロファイラーに依存しています。
IDisposable パターンを使用することをお勧めします。
[Test]
public void CreateName_AddsCurrentTimeAtEnd()
{
using (Clock.NowIs(new DateTime(2010, 12, 31, 23, 59, 00)))
{
string name = new ReportNameService().CreateName(...);
Assert.AreEqual("name 2010-12-31 23:59:00", name);
}
}
ここで詳しく説明されています:http://www.lesnikowski.com/blog/index.php/testing-datetime-now/
簡単な答え:System.DateTime を捨ててください:) 代わりに、使用してください 野田時間 そしてそれはテストライブラリです: NodaTime.Testing.
参考文献:
クラスを注入することもできます (より良い方法:方法/代表者) に使用します DateTime.Now
テスト中のクラスで。持っている DateTime.Now
はデフォルト値にし、テスト時にのみ定数値を返すダミー メソッドに設定します。
編集: ブレア・コンラッドが言ったこと (彼は見るべきコードをいくつか持っています)。ただし、私はこのためにデリゲートを好む傾向があります。デリゲートは、次のようなものでクラス階層を乱雑にしないからです。 IClock
...
私はこの状況に頻繁に直面したため、次のことを明らかにする単純な nuget を作成しました。 今 インターフェースを介したプロパティ。
public interface IDateTimeTools
{
DateTime Now { get; }
}
もちろん実装は非常に簡単です
public class DateTimeTools : IDateTimeTools
{
public DateTime Now => DateTime.Now;
}
したがって、プロジェクトにnugetを追加した後、単体テストで使用できるようになります
モジュールは、GUI Nuget パッケージ マネージャーから直接インストールするか、次のコマンドを使用してインストールできます。
Install-Package -Id DateTimePT -ProjectName Project
Nuget のコードは次のとおりです。 ここ.
Autofacとの使用例はこちら ここ.
デバッグ/デプロイ中に何が起こるかを制御するために条件付きコンパイルを使用することを検討したことがありますか?
例えば
DateTime date;
#if DEBUG
date = new DateTime(2008, 09, 04);
#else
date = DateTime.Now;
#endif
それができない場合は、プロパティを公開して操作できるようにする必要があります。これはすべて、作成の課題の一部です。 テスト可能 コード、これは私が現在自分自身で格闘しているものです:D
編集
私の大部分はそれを好むでしょう ブレア首相のアプローチ. 。これにより、コードの一部を「ホットプラグ」してテストに役立てることができます。すべては設計原則に従っています 変化するものをカプセル化する テスト コードは運用コードと何ら変わりません。ただ、外部からは誰もそれを見ないだけです。
ただし、この例では作成とインターフェイスが大変な作業のように思えるかもしれません (これが、条件付きコンパイルを選択した理由です)。