web-dev-qa-db-ja.com

ファイルをディスクにユニットテストするにはどうすればよいですか?

ユニットテストを実行することを強くお勧めします ファイルシステムから分離して、テストでファイルシステムに触れると、ファイルシステム自体もテストするためです。 OK、それは合理的です。
私の質問は、ディスクへのファイル保存をテストしたい場合、どうすればよいですか?データベースと同様に、データベースアクセスを担当するインターフェイスを分離してから、テスト用にこれの別の実装を作成しますか?それとも他の方法があるのでしょうか?

22
chester89

これに対する私のアプローチは、私が今読んだばかりの Growing Object-Oriented Software Guided by Tests (GOOS)の本に大きく偏っていますが、それは私が今日知っている中で最高です。具体的には:

  • コードからファイルシステムを抽象化するためのインターフェイスを作成します。このクラスが共同作業者/依存関係として必要な場所でモックします。これにより、ユニットテストとフィードバックが迅速に保たれます。
  • インターフェイスの実際の実装をテストする統合テストを作成します。つまり、Save()を呼び出すと、実際にファイルがディスクに保持され、書き込み内容が含まれていることを確認します(参照ファイルを使用するか、ファイルに含まれるべきいくつかの項目について解析します)
  • システム全体をエンドツーエンドでテストする受け入れテストを作成します。ここでは、ファイルが作成されたことを確認できます。このテストの目的は、実際の実装が正しく配線/接続されているかどうかを確認することです。

コメント投稿者向けの更新:

構造化データ(ブックオブジェクトなど)を読んでいる場合(IEnumerableの代わりに文字列を使用しない場合)

interface BookRepository
{
  IEnumerable<Books> LoadFrom(string filePath);
  void SaveTo(string filePath, IEnumerable<Books> books);
}

これで、constructor-injectionを使用して、クライアントクラスにモックを注入できます。したがって、クライアントクラスユニットテストは高速です。 ファイルシステムにヒットしない。依存関係で適切なメソッドが呼び出されていることを確認するだけです(例:ロード/保存)

var testSubject = new Client(new Mock<BookRepository>.Object);

次に、ファイル(または、必要に応じて明日SQL DB)で機能するBookRepositoryの実際の実装を作成する必要があります。他の誰も知る必要はありません。 FileBasedBookRepository(上記のロールを実装する)の統合テストを記述し、参照ファイルを使用してLoadを呼び出すと適切なオブジェクトが得られ、既知のリストを使用してSaveを呼び出すと、それらがディスクに保持されることをテストします。 。つまり、実際のファイルを使用しますこれらのテストは遅いので、タグでマークアップするか、別のスイートに移動します。

[TestFixture]
[Category("Integration - Slow")]
public class FileBasedBookRepository 
{
  [Test]
  public void CanLoadBooksFromFileOnDisk() {...}
  [Test]
  public void CanWriteBooksToFileOnDisk() {...}
}

最後に、ロードと保存を実行する1つ以上の受け入れテストがあるはずです。

40
Gishu

保存関数にファイル名を渡す代わりに、Stream、TextWriterなどを渡すことができます。次に、テスト時にメモリベースの実装に合格し、実際にディスクに何も書き込まずに正しいバイトが書き込まれていることを確認できます。

問題と例外をテストするために、モックフレームワークを見ることができます。これは、保存プロセスの特定の時点で特定の例外を人為的に生成し、コードがそれを適切に処理することをテストするのに役立ちます。

6
Mark Byers

ファイルI/Oを実行する単体テストは速度が遅すぎる傾向があるため、これらの単体テストの作成には注意が必要です。ただし、単体テストでのファイルI/Oの絶対的な禁止はありません。

単体テストでは、一時ディレクトリを設定および破棄し、その一時ディレクトリ内にテストファイルとディレクトリを作成します。はい、テストは純粋なCPUテストよりも遅くなりますが、それでも高速です。 JUnitには、まさにこのシナリオを支援するサポートコードもあります:a @RuleTemporaryFolder

そうは言っても、ほとんどのファイル書き込みコードは次の形式を取ります。

  1. ファイルへの出力ストリームを開きます。このコードは、不足しているファイルまたはファイルのアクセス許可の問題に対処する必要があります。ファイルを開き、それらの障害状態に対処することをテストする必要があります。
  2. 出力ストリームに書き込みます。これは正しい形式で記述する必要があります。これは、最も多くのテストを必要とする最も複雑な部分です。実際にはまれですが、出力ストリームへの書き込み中のI/Oエラーに対処する必要があります。
  3. 出力ストリームを閉じます。これは、ストリームを閉じる際のI/Oエラーに対処する必要がありますが、これらのエラーは実際にはまれです。

最初のものだけが実際にファイルシステムを扱います。残りは出力ストリームを使用するだけです。

したがって、名前付きファイルではなく、特定の出力ストリームを操作する独自のメソッド(関数)に中間部分と最後の部分を抽出できます。次に、その出力ストリームをモックして、メソッドの単体テストを行うことができます。これらの単体テストは非常に高速になります。ほとんどのプログラミング言語は、すでに適切な出力ストリームクラスを提供しています。

それはユニットテストされる最初の部分だけを残します。必要なテストはわずかなので、テストスイートは許容できるほど高速である必要があります。

3
Raedwald