web-dev-qa-db-ja.com

.NETで実際のファイルを使用して統合テストを行う方法

ファイルシステムとファイルに関連するロジックを実装するクラスがいくつかあります。たとえば、このロジックの一部として次のタスクを実行しています。

  • 特定のフォルダーが特定の構造を持っているかどうかを確認します(たとえば、特定の名前のサブフォルダーが含まれているなど)。
  • それらのフォルダーからいくつかのファイルをロードし、その構造を確認します(たとえば、これらは特定のフォルダー内の特定の場所にあるいくつかの構成ファイルです)
  • 構成ファイルからテスト/検証用の追加ファイルをロードします(たとえば、この構成ファイルには、同じフォルダー内の他のファイルに関する情報が含まれ、他の内部構造などが必要です...)

現在、このロジックにはすべて何らかのワークフローがあり、何かが正しくない場合は例外がスローされます(たとえば、構成ファイルが特定のフォルダーの場所で見つからないなど)。さらに、このロジックには Managed Extensibility Framework(MEF) が関係しています。チェックするこれらのファイルの一部は、手動でMEFアグリゲートなどにロードするマネージDLLであるためです。

ここで、これらすべてを何らかの方法でテストしたいと思います。さまざまなテストケースをカバーし、それらに対してコードを実行するHDD上に、いくつかの物理的なテストフォルダーを作成することを考えていました。たとえば、作成できます:

  • 正しい構造とすべてのファイルが有効なフォルダー
  • 構造は正しいが、構成ファイルが無効なフォルダー
  • 正しい構造のフォルダですが、構成ファイルなどがありません...

これは正しいアプローチでしょうか?しかし、このシナリオでコードをどのように正確に実行するかはわかりません...アプリケーション全体を実行して、これらのモックされたフォルダをチェックするように指示するのは確かにしたくないです。これらのファイルシステムオブジェクトに対してコードを実行する一種の「ユニットテスト」を作成するために、ユニットテストフレームワークを使用する必要がありますか?

一般的に、これはすべてこの種のテストシナリオの正しいアプローチですか?他のより良いアプローチはありますか?

54
matori82

まず第一に、それは外部リソースに触れることなくロジックをテストするユニットテストを書く方が良いと思う。ここには2つのオプションがあります。

  1. 抽象化レイヤーを使用して、ファイルシステムなどの外部依存関係からロジックを分離する必要があります。この抽象化は、ユニットテストで簡単にスタブまたはモックできます(手作業で、またはNSubstitute、FakeItEasy、Moqなどの制約付き分離フレームワークを使用して)。私はこのオプションを好む、なぜならこの場合はより良いデザインにあなたをプッシュするテストをするからだ。
  2. レガシコード(この場合のみ)を処理する必要がある場合は、ほとんどすべて(たとえば、密封された静的な)クラス、非仮想メソッド)。しかし、彼らはお金がかかります。 Visual Studio 2012/2013 Premium/Ultimateの幸せな所有者でない限り、唯一の「無料」オプションはMicrosoft Fakesです。

単体テストでは、MEFなどの外部ライブラリのロジックをテストする必要はありません。

二番目に、あなたが統合テストを書きたいなら、あなたは「幸せなパス」テスト(すべてがOKであるとき)と境界ケースであなたのロジックをテストするいくつかのテストを書く必要があります(ファイルまたはディレクトリが見つかりません)。 @Sergey Berezovskiyとは異なり、テストケースごとに個別のフォルダーを作成することをお勧めします。主な利点は次のとおりです。

  1. 意図をより明確に表す意味のある名前をフォルダーに付けることができます。
  2. 複雑な(つまり、壊れやすい)セットアップ/分解ロジックを記述する必要はありません。
  3. 後で別のフォルダ構造を使用することにした場合でも、作業コードとテストが既にあるため、より簡単に変更できます(テストハーネスでのリファクタリングははるかに簡単です)。

単体テストと統合テストの両方について、-通常の単体テストフレームワークを使用できます(NUnitやxUnit.NETなど)。このフレームワークを使用すると、ビルドサーバー上の継続的統合シナリオでテストを簡単に起動できます。

両方の種類のテストを作成する場合、統合テストからユニットテストを分離する必要があります(テストの種類ごとに個別のプロジェクトを作成できます)。その理由:

  1. ユニットテストは、開発者向けのセーフティネットです。最後のコード変更(バグ修正、新機能)後のシステムユニットの予想される動作に関する迅速なフィードバックを提供する必要があります。それらが頻繁に実行される場合、開発者はシステムを壊したコードの一部をすばやく簡単に識別できます。遅いユニットテストを実行したい人はいません。
  2. 統合テストは、一般に単体テストよりも低速です。しかし、それらは異なる目的を持っています。彼らは、実際の依存関係でユニットが期待通りに動作することをチェックします。
59
Vladimir Almaev

インターフェイスの背後にあるファイルシステムへの呼び出しを抽象化することにより、ユニットテストでできるだけ多くのロジックをテストする必要があります。依存性注入と FakeItEasy などのテストフレームワークを使用すると、ファイルやフォルダーを操作するためにインターフェイスが実際に使用または呼び出されていることをテストできます。

ただし、ある時点で、ファイルシステムで動作する実装もテストする必要があります。これは統合テストが必要な場所です。

テストする必要があるのは、比較的孤立しているようです。テストするのは、独自のファイルシステム上の独自のファイルとディレクトリだけです。データベース、または複数のユーザーを含む他の外部システムなどをテストしたい場合、事態はより複雑になる可能性があります。

このタイプの統合テストを最適に実行するための「公式ルール」を見つけるとは思わないが、あなたは正しい軌道に乗っていると思う。努力すべきいくつかのアイデア:

  • 明確な基準:各テストのルールと目的を完全に明確にします。
  • 自動化:手動で調整しすぎることなく、テストを迅速に再実行する機能。
  • 再現性:「リセット」できるテスト状況。これにより、わずかな変動でテストを迅速に再実行できます。

繰り返し可能なテストシナリオを作成する

あなたの状況では、2つのメインフォルダーを設定します。1つはすべてが本来の状態(つまり正常に動作している)であるフォルダーと、すべてのルールが破られているフォルダーです。

これらのフォルダーとその中のファイルを作成してから、各フォルダーをZip圧縮し、テストクラスでロジックを作成して、各フォルダーを解凍します。

これらは実際にはテストではありません。代わりに、テストシナリオを設定するための「スクリプト」と考えてください。これにより、テスト中にメインの統合テストが変更または混乱した場合でも、フォルダーとファイルを簡単かつ迅速に削除および再作成できます。それらをテストクラスに入れる理由は、テスト中に作業するのと同じインターフェースから簡単に実行できるようにするためです。

テスト中

状況ごとに1セットずつ、テストクラスの2つのセットを作成します(正しく設定されていないフォルダーと、壊れたルールのフォルダー)。これらのテストを、自分にとって意味のあるフォルダーの階層に配置します(状況の複雑さに応じて)。

ユニット/統合テストにどれだけ精通しているかは明らかではありません。いずれにせよ、 NUnit をお勧めします。 Shouldの拡張機能も使用したいです。これらの両方をNugetから取得できます。

install-package Nunit
install-package Should

Should-packageを使用すると、次のような方法でテストコードを記述できます。

someCalculatedIntValue.ShouldEqual(3); 
someFoundBoolValue.ShouldBeTrue();

テストを実行するために、いくつかのテストランナーが利用可能であることに注意してください。個人的には、Resharperに組み込まれたランナーで実際の経験しかありませんでしたが、私はそれに非常に満足しており、推奨することに問題はありません。

以下は、2つのテストを含む簡単なテストクラスの例です。 1つ目では、Shouldの拡張メソッドを使用して期待値をチェックしますが、2つ目では何も明示的にテストしないことに注意してください。これは、[ExpectedException]でタグ付けされているためです。つまり、テストの実行時に指定されたタイプの例外がスローされないと失敗します。これを使用して、ルールの1つが破られたときに適切な例外がスローされることを確認できます。

[TestFixture] 
public class When_calculating_sums
{                    
    private MyCalculator _calc;
    private int _result;

    [SetUp] // Runs before each test
    public void SetUp() 
    {
        // Create an instance of the class to test:
        _calc = new MyCalculator();

        // Logic to test the result of:
        _result = _calc.Add(1, 1);
    }

    [Test] // First test
    public void Should_return_correct_sum() 
    {
        _result.ShouldEqual(2);
    }

    [Test] // Second test
    [ExpectedException(typeof (DivideByZeroException))]
    public void Should_throw_exception_for_invalid_values() 
    {
        // Divide by 0 should throw a DivideByZeroException:
        var otherResult = _calc.Divide(5, 0);
    }       

    [TearDown] // Runs after each test (seldom needed in practice)
    public void TearDown() 
    {
        _calc.Dispose(); 
    }
}

これらすべての準備が整ったら、テストシナリオを作成および再作成し、それらに対して簡単かつ繰り返し可能な方法でテストを実行できるはずです。


編集:コメントで指摘されているように、 Assert.Throws()は別のオプションです 必要に応じて例外がスローされることを保証します。個人的には、タグバリアントが好きですが、 with parameters 、エラーメッセージなども確認できます。別の例(カスタムエラーメッセージが電卓からスローされていると仮定):

[ExpectedException(typeof(DivideByZeroException), ExpectedMessage="Attempted to divide by zero" )]
public void When_attempting_something_silly(){  
    ...
}
8
Kjartan

単一のテストフォルダーを使用します。さまざまなテストケースでは、コンテキストセットアップの一部として、そのフォルダーに異なる有効/無効ファイルを配置できます。テストの分解では、これらのファイルをフォルダーから削除するだけです。

例えば。 Specflow

Given configuration file not exist
When something
Then foo

Given configuration file exists
And some dll not exists
When something
Then bar

適切なファイルをフォルダーにコピーする/しないとして、各コンテキスト設定手順を定義します。 table を使用して、フォルダーにコピーするファイルを定義することもできます。

Given some scenario
| FileName         |
| a.config         |
| b.invalid.config |
When something
Then foobar
3

あなたのプログラムのアーキテクチャが良いアドバイスを与えるのを知りませんが、試してみます

  1. 実際のファイル構造をテストする必要はないと信じています。ファイルアクセスサービスはシステム/フレームワークによって定義され、テストする必要はありません。関連するテストでこのサービスをモックする必要があります。
  2. また、MEFをテストする必要もありません。すでにテスト済みです。
  3. SOLID原則を使用して単体テストを行います。特に単一責任原則を見てください。これにより、相互に関連しない単体テストを作成できます。依存関係を避けるために、モックを忘れないでください。
  4. 統合テストを行うために、ヘルパークラスのセットを作成できます。これは、テストするファイル構造のシナリオをエミュレートするです。これにより、このテストを実行するマシンに接続されないままになります。このようなアプローチは、実際のファイル構造を作成するよりも複雑かもしれませんが、私は気に入っています。
1
Max

フレームワークロジックを構築し、同時実行の問題とファイルシステムの例外をテストして、明確に定義されたテスト環境を確保します。

問題のあるドメインのすべての境界をリストしてみてください。数が多すぎる場合は、問題が広範に定義されすぎており、分解する必要がある可能性を考慮してください。システムがすべてのテストに合格するために必要な必要十分条件の完全なセットは何ですか?次に、すべての条件を調べて、個々の攻撃ポイントとして扱います。そして、あなたが考えることができるすべての方法をリストし、それを破る。あなたがそれらをすべて見つけたことを自分自身に証明してみてください。次に、それぞれのテストを作成します。

最初に環境について上記のプロセスを実行し、最初に満足のいく標準に合わせてそれを構築およびテストしてから、ワークフロー内のより詳細なロジックを作成します。テスト中に環境と詳細なロジックとの間に依存関係が発生した場合、反復が必要になる場合があります。

0
Brad Thomas