web-dev-qa-db-ja.com

ファイルのパーサーを単体テストする方法は?

すべての形式の画像ファイルのメタデータパーサーを実装しています。そのためのテストを書きたいです。これを行う簡単な方法の1つは、すべての形式のテスト画像ファイルをテスト用のリソースとして用意し、実際にそれらを入力として読み取ることです。このアプローチはうまくいくかもしれませんが、単体テストの方法論から理解する限り、単体テストはI/Oを実行すべきではありません。そうするのに良い方法ですか、それとも代替手段ですか?

9
Sanich

そのためのテストを書きたいです。

何をテストするつもりですか

TDDを使いたい。パーサーをリファクタリングしていて、 'parse()'メソッドをテストしたいと思います。

したがって、目的は物事をクリーンアップすることです。

レガシーコードのリファクタリングは、TDDで100%準拠しているとは言えません。

悪いコードはテストを制限します。

さらに重要なことは、コードをクリーンアップする意図(ドライブ-コードを変更する理由)は、コードがビジネスドメインに関係することを行う本来の意図とは異なることです。


ステップ1

私は、ほとんどの機能をカバーするずさんな統合テストから始めます。

飼料は原油の投入をテストします-例これらの50MBのリソースファイル。
洗練された出力のみを要求し、内部のものを無視します。

それは実際に重要です-より高いテスト抽象化は実装制限を緩めるものです。

これによりセーフティネットが提供されるので、コードを開いて恐怖のないリファクタリングを行うことができます。

ステップ2

これが完了したら、実際に作業を開始してリファクタリングする準備が整います。

コードを読み取るsmallを開始します。 ( 良い本

コードのフォーマット、余分な空白の削除、冗長な変数プレフィックスの削除など。

次に、構造の変更に進みます-必要に応じてメソッド、インターフェース、クラスを抽出します。
そして、分割して征服するだけでなく、「理にかなう」™ものを組み合わせてみてください。

適切なコード構造でのみ、機能の分離されたユニットのユニットテストを記述できます。

開始した統合テストのパフォーマンスが十分であれば、ユニットテストネットワークを構築する手間はかかりません。

どちらの方法でも-適切なコード構造により、自然でスタブ化が容易なI/Oシームにつながります。

単体テストのネットワークが十分に強力になったら、統合テストを削除します。
または単体テストと同じ方法で入力をスタブします(一種の減価償却統合テスト)。

10
Arnis Lapsa

このアプローチは機能するかもしれませんが、単体テストの方法論から理解する限り、単体テストはI/Oを実行すべきではありません

ここの部屋で象を見落としていると思います。あなたのparserI/Oを実行すべきではありません

パーサージョブはデータの解析であり、ディスクI/Oではありません。どの言語を使用していても、おそらくstreamの概念があります。これが、パーサーがデータを取得する場所です。そして、それがFileStreamMemoryStreamstd::cinはパーサービジネスではありません。

テストに戻りましょう。問題はありません。テストには、パーサーをテストするためのデータが含まれます。そして、それが個別のファイル、リソースマニフェストストリーム、またはハードコードされたバイト配列でディスクに書き込まれるかどうかは、特に問題ではありません。 CI自動化はそれで機能する必要があり、それだけが知っておく必要があります。最後に、データisがディスク上にあります。他にどこにあるのでしょうか?実行コードもディスクから読み込まれます。

つまり、パーサーをディスクアクセスから独立させる必要があります。関心事の分離。次に、CIソリューションにとって最もエレガントな方法でテストをデータにロードして、他の誰かが自分のマシンで実行してもテストが失敗しないようにします(他の誰かがCIエージェントである可能性があります)。 どのようにあなたがそれを達成するかは、それが機能する限り、誰も本当に気にしない詳細です。

6
nvoigt

単体テストの方法論から理解する限り、テストはI/Oを実行すべきではありません

どの種類のテストについて話しているのかを区別する必要があります。

  • 小さな"write test"-"write code"-"refactor"サイクルでTDDを使用する場合、理想的には小さなデータセットでI/Oなしの非常に迅速なユニットテストが必要です。

  • 特に複数の外部イメージを使用して受け入れテストまたは統合テストを行う場合は、ファイルとI/Oを使用するだけで十分ではありませんが、おそらく必要です。

だからあなたが書いているテストの種類に基づいて決定します。

4
Doc Brown

私があなたを正しく理解していれば、あなたには2つの目標があります

  • 既存のコードに新しい機能/機能/リファクタリングを追加する
  • 既存のコードを拡張/再構成する際に、現在の機能が損なわれないことを確認してください(「回帰テスト」または安全ネット)。

私があなたなら、ファイルベースの統合テストとして、「現在の機能が壊れないことを確認する」部分から始めます

  • サンプル画像のフォルダ
  • 各画像には、正確な結果のテキスト表現があります。

この(おそらく遅い)回帰テストは、すべての画像ファイルを反復処理し、解析を期待されるファイルと比較します。

これが機能したら、提案された回帰テストとはまったく異なる新しい機能を備えたTDDについて考えることができます。

質問のTDDの部分については、コードを確認せずにお手伝いすることはできません。 (質問が広すぎます)

1
k3b

パーサーをテストする場合、少なくとも2つのテストモードが必要です。

  • マイクロでのテスト:パーサーは、フレーズ(バイトまたは文字のセット)を解析する方法を知っています。
  • マクロでのテスト:パーサーはフレーズのセット全体を解析する必要があります

パーサーがモーダルである場合、モード遷移が適切に処理されることを確認するためのテストがあります。

肝心なのは、2つの異なるテストセットであることです。通常、私はこのように作業を分割することになります:

  • 単体テストは、個々のフレーズをテストするためのものです
  • 統合テストは、ファイル全体、および問題のケースをテストするためのものです

通常、マイクロテスト用にメモリ内のバイトを設定し、統合テストにファイルシステムを使用できます。これは私に役立っています。とは言っても、テキストパーサー(私が行ったことの大部分)のテストフレーズを作成する方が、バイナリパーサーで同じことを行うよりもはるかに簡単です。私はいくつかのバイナリ解析を行ったので、私のテストケースはおそらくバイトの魔法の配列のように見えるでしょうが、それはうまくいきます。

1
Berin Loritsch

パーサーは構文ベースのトランスレータであり、基本文法に準拠する一連の入力単語を出力「単語」に変換します。単語は通常、一連のアクションの形式をとります。

パーサーを逆にします。基本言語を認識し、認識した入力単語の出力を生成する代わりに、基本言語の単語を生成し、それをパーサーにフィードします。つまり、元のパーサーの各状態で、入力ワードを受け入れてその有効性をテストし、受け入れられた入力に対して適切なアクションを実行する代わりに、リバースパーサーが確率メトリックを使用して入力ワード(有効と無効の両方)を生成します。したがって、これは、基本言語+エラー(ストレステスト用)を生成する乱数ジェネレーターをフィードする確率的オートマトンになります。

したがって、テスターは、基本言語で単語を生成するための、逆に実行されるパーサーそのものです。実際には、乱数ジェネレーターのランダムストリームを、それ自体が(意図的に生成されたエラーは別として)パーサーの入力言語である出力言語に変換するトランスレーターになります。

テストスイートは、自動的に生成された乱数シードのセットで構成されます。パーサーの各更新は、自動的に生成された同じテストケースの同じセットで、前のバージョンに対して回帰テストされます。

テストケースは、元のパーサーを逆にして生成されます。ただし、十分に開発した後、アップグレードしたパーサーを逆にして、代わりにそれをテストジェネレーターとして使用できます。次に、テストジェネレーターを前のテストジェネレーターに対して実行して回帰テストを行い、乱数ジェネレーターから同じテストケースが生成されることを確認する必要があります。その過程で、アップグレードされたパーサーを設計して、簡単に元に戻すことができます。

したがって、テスターとパーサーの両方が並んで配置されます。

0
Rock Brentwood