時間に依存する機能に取り組んでいるとき...単体テストはどのように整理しますか?単体テストのシナリオがプログラムの「今」を解釈する方法に依存する場合、どのように設定しますか?
2番目の編集:数日後にあなたの経験を読んでください
この状況に対処するための手法は、通常、次の3つの原則の1つを中心に展開することがわかります。
- 依存関係を追加(ハードコード)する:時間関数/オブジェクトの上に小さなレイヤーを追加し、常にこのレイヤーを通じてdatetime関数を呼び出します。このようにして、テストケース中に時間を制御できます。
- モックを使用する:コードはまったく同じままです。テストでは、時間オブジェクトを偽の時間オブジェクトに置き換えます。ソリューションには、プログラミング言語によって提供される本物の時刻オブジェクトを変更することが含まれる場合があります。
- 依存性注入を使用する:時間参照がパラメーターとして渡されるようにコードをビルドします。次に、テスト中にパラメーターを制御します。
言語に固有の手法(またはライブラリ)は大歓迎であり、例示的なコードが追加されれば強化されます。次に、どのプラットフォームに同様の原則を適用できるかが最も興味深い。そしてはい... PHPですぐにそれを適用できる場合は、より良いよりも優れています;)
簡単な例から始めましょう:基本的な予約アプリケーションです。
JSON APIと、リクエストメッセージと確認メッセージの2つのメッセージがあるとします。標準的なシナリオは次のようになります。
- あなたはリクエストをします。トークンを使用して応答を取得します。その要求を満たすために必要なリソースは、システムによって5分間ブロックされます。
- トークンで識別されるリクエストを確認します。トークンが5分以内に発行された場合、トークンは受け入れられます(リソースは引き続き使用可能です)。5分以上経過した場合は、新しいリクエストを作成する必要があります(リソースは解放されています。リソースの可用性をもう一度確認する必要があります)。
ここに対応するテストシナリオがあります:
お願いします。受け取ったトークンで(すぐに)確認します。私の確認は受け入れられました。
お願いします。3分待ちます。受け取ったトークンで確認します。私の確認は受け入れられました。
お願いします。6分待ちます。受け取ったトークンで確認します。確認が拒否されました。
これらの単体テストをプログラムするにはどうすればよいですか?これらの機能をテストできるようにするには、どのアーキテクチャを使用する必要がありますか?
編集注:リクエストを送信すると、時間はミリ秒に関する情報を失う形式でデータベースに保存されます。
余計ですが、少し冗長かもしれません。ここで、「宿題」をやっていることがわかった詳細について説明します。
私は自分の時間関数に依存して機能を構築しました。私の関数VirtualDateTimeには、新しいDateTime()を呼び出すために使用した静的メソッドget_time()があります。この時間機能を使用すると、「今」の時間をシミュレートおよび制御できるため、「今すぐ21st jan 2014 16h15に設定します。リクエストを送信します。3分先に進みます。確認を行います」のようなテストを作成できます。これは、依存関係を犠牲にしてうまく機能し、「それほどきれいではないコード」です。
もう少し統合されたソリューションは、追加の「仮想時間」機能を備えたDateTimeを拡張する独自のmyDateTime関数を構築することです(最も重要なことは、今、私が望むものに設定することです)。これにより、最初のソリューションのコードは少しエレガントになりますが(新しいDateTimeではなく新しいmyDateTimeを使用)、非常によく似たものになります。独自のクラスを使用して機能を構築する必要があるため、依存関係が作成されます。
でも、DateTime関数をハッキングして、必要なときに依存関係で機能させるようにしています。ただし、クラスを別のクラスで置き換える簡単で便利な方法はありますか?(私はそれに対する答えを得たと思います:下記の名前空間を参照してください)。
PHP環境では、「Runkit」を読んで、これを動的に行うことができます(DateTime関数をハックする)。DateTimeに関する変更を行う前に、テスト環境で実行していることを確認し、運用環境でそのままにしておくことができるのはすばらしいことです[1]。これは、手動のDateTimeハックよりはるかに安全でクリーンに聞こえます。
時間を使用する各クラスの時計関数の依存性注入[2]。これはやりすぎではありませんか?一部の環境では非常に役立つことがわかります[5]。この場合、私はあまり好きではありませんが。
すべての関数の時間の依存関係を削除します[2]。これは常に実現可能ですか?(以下のその他の例を参照)
名前空間の使用[2] [3]?これはかなりよさそうだ。\ DateTimeを拡張する独自のDateTime関数でDateTimeをモックすることができます... DateTime :: setNow( "2014-01-21 16:15:00")とDateTime :: wait( "+ 3 minutes")を使用します。これは私が必要とするもののほとんどをカバーしています。しかし、time()関数を使用するとどうなりますか?または他の時間関数?私はまだ元のコードでの使用を避けなければなりません。または、コードで使用しているPHP時間関数がテストでオーバーライドされていることを確認する必要があります...これだけを実行できるライブラリはありますか?
スレッドのためだけにシステム時間を変更する方法を探していました。何もないようです [4]。これは残念です。「このスレッドの2014年1月21日16h15に時間を設定する」ための「単純な」PHP関数は、この種のテストに最適な機能です。
exec()を使用して、テストのシステム日時を変更します。これは、サーバー上の他のものを台無しにすることを恐れていない場合に機能します。また、テストを実行した後、システム時間を戻す必要があります。それはいくつかの状況でトリックを行うことができますが、かなり「ハッキー」に感じます。
これは私にとって非常に標準的な問題のように見えます。しかし、それを処理するための単純で一般的な方法がまだありません。多分私は何かを逃した?あなたの経験を共有してください!
注:同様のテストが必要になる可能性がある他の状況をいくつか示します。
なんらかのタイムアウトで機能する機能(チェスゲームなど)
特定の時間にイベントをトリガーするキューを処理します(上記のシナリオでは、次のように続けることができます。予約が始まる1日前に、すべての詳細をメールでユーザーに送信します。テストシナリオ...)
過去に収集されたデータを使用してテスト環境をセットアップしたい-今のように表示したい。[1]
1 /programming/3271735/simulate-different-server-datetimes-in-php
2 /programming/4221480/how-to-change-current-time-for-unit-testing-date-functions-in-php
3 http://www.schmengler-se.de/en/2011/03/php-mocking-built-in-functions-like-time-in-unit-tests/
DateTime now
、クロックではなくコードに値を渡す方が簡単です。