私は、Eclipseコンパイラを使用してJava ASTを別の言語であるOpenCLに変換するプロジェクトに携わっていますが、同様の問題があります。
私はあなたのための魔法の解決策を持っていませんが、それが役立つ場合に備えて私の経験を共有します。
期待される出力(output.txtを使用)でテストするあなたのテクニックも私が始めた方法ですが、それはテストの絶対的なメンテナンスの悪夢になりました。何らかの理由でジェネレーターまたは出力を変更しなければならなかったとき(これは数回発生しました)、予想されるすべての出力ファイルを書き直さなければなりませんでした-そしてそれらは大量にありました。すべてのテストに失敗することを恐れて、出力をまったく変更したくありませんでしたが(これは悪いことでした)、最終的にはそれらを破棄し、代わりに結果のASTでテストを行いました。これは、出力を「大まかに」テストできることを意味しました。たとえば、ifステートメントの生成をテストしたい場合は、生成されたクラスで唯一のifステートメントを見つけて(この一般的なASTのすべてを実行するヘルパーメソッドを作成しました)、それについていくつかのことを確認し、終わり。そのテストはしませんでした クラスの名前の付け方や、追加の注釈やコメントがあるかどうかを気にします。テストがより集中されたので、これは非常にうまく機能することになりました。欠点は、テストがコードとより緊密に結合されていることです。そのため、Eclipseコンパイラ/ ASTライブラリを取り除いて他のものを使用したい場合は、すべてのテストを書き直す必要があります。結局、コード生成は時間とともに変化するので、私はその代償を払っても構わないと思っていました。
また、統合テスト(生成されたコードをターゲット言語で実際にコンパイルして実行するテスト)にも大きく依存しています。これらのタイプのテストは、単体テストよりもはるかに多く、より有用でより多くの問題をキャッチできるように見えたためです。
訪問者のテストについても、さらに統合スタイルのテストを行います。非常に小さい/特定のJavaソースファイルを取得し、Eclipseコンパイラでロードし、訪問者の1人を実行して結果を確認します。Eclipseコンパイラーを呼び出さずにテストする他の唯一の方法は、AST全体をモックアウトすることですが、これは実現不可能でした。ほとんどの訪問者は重要であり、メインクラスからアノテーションを読み取るため、完全に構築された有効なJavaASTが必要でした。 。ほとんどの訪問者は、小さなOpenCLコードフラグメントを生成したか、単体テストで検証できるデータ構造を構築したため、この方法でテスト可能でした。
はい、私のすべてのテストはEclipseコンパイラと非常に緊密に結合されています。しかし、私たちが書いている実際のソフトウェアもそうです。他のものを使用するということは、とにかくプログラム全体を書き直さなければならないことを意味するので、それは私たちがかなり喜んで支払う代償です。解決策は1つではないと思います。密結合のコストと、テストの保守性/シンプルさを比較検討する必要があります。
また、デフォルト設定でEclipseコンパイラをセットアップする、メソッドツリーの本体ノードを引き出すコードなど、かなりの量のテストユーティリティコードがあります。テストをできるだけ小さくするようにしています(これはおそらく常識ですが、おそらく言及する価値があります)。
(コメントへの応答における以下の編集/追加-コメントの応答よりも読みやすく/フォーマットしやすい)
「私は統合テストにも大きく依存しています。生成されたコードをターゲット言語で実際にコンパイルして実行するテストです」これらのテストは実際に何をしましたか?output.txtテストとはどのように異なりますか?
(もう一度編集:質問を読み直した後、私たちのアプローチは同じであることがわかりましたので、これは無視してください)
統合テストでは、ソースコードを生成して最初に行った期待される出力と比較するのではなく、OpenCLコードを生成し、コンパイルして実行します。生成されたすべてのコードが出力を生成し、その出力が比較されます。
たとえば、ジェネレータが正しく機能する場合、2つのバッファの値を合計して3番目のバッファに値を配置するOpenCLコードを生成するJavaクラスがあります。最初は、予想されるOpenCLコードを使用してテキストファイルを作成し、テストでそれを比較していました。ここで、統合テストはコードを生成し、OpenCLコンパイラーを介して実行し、実行してから、テストで値をチェックします。
「訪問者のテストについても、統合スタイルのテストをさらに行います。非常に小さい/特定のJavaソースファイルを取得し、Eclipseコンパイラでロードし、訪問者の1人を実行して、結果を確認します。」訪問者の1人と一緒に、またはテストしたい訪問者まですべての訪問者を実行しますか?
訪問者のほとんどは、互いに独立して実行することができます。可能な場合は、テストしている訪問者のみで実行するか、他の訪問者に依存している場合は、最小限の訪問者セットが必要です(通常は他の1人だけが必要です)。訪問者は互いに直接話すことはありませんが、渡されるコンテキストオブジェクトを使用します。これらは、テストで人為的に構築して、物事を既知の状態にすることができます。
他の質問ですが、このプロジェクトではモックを使用していますか?さらに、他のプロジェクトでモックを定期的に使用していますか?私は話している相手について明確な画像を取得しようとしています:P
このプロジェクトでは、テストの約5%でモックを使用しますが、おそらくそれよりも少なくなります。そして、私はEclipseコンパイラーのものをモックアウトしません。
モックの問題は、私がモックアウトしているものをよく理解する必要があるということですが、Eclipseコンパイラの場合はそうではありません。呼び出されるビジターメソッドはたくさんありますが、どちらを呼び出すべきかわからない場合があります(たとえば、ExtendedStringLiteralにアクセスするか、StringLiteralにアクセスして文字列リテラルを呼び出しますか?)これをモックアウトして、どちらかを想定した場合、これは現実に対応していない可能性があり、テストに合格してもプログラムは失敗します-望ましくありません。私たちが行う唯一のモックは、アノテーションプロセッサAPIのカップル、Eclipseコンパイラアダプタのカップル、および独自のコアクラスのいくつかです。
Java EEのものなどの他のプロジェクトでは、より多くのモックが使用されましたが、私はまだそれらの熱心なユーザーではありません。APIがより明確に、理解され、予測可能であるほど、モックの使用を検討する可能性が高くなります。
プログラムの最初のフェーズは、通常のコンパイラと同じです。ソースファイルから情報を抽出し、(大きくて複雑な!)シンボルテーブルを埋めます。これをシステムテストするにはどうしますか?理論的には、ソースファイルとsymbolTableに関するすべての情報を含むsymbolTable.txt(または.xmlなど)を使用してテストを作成できますが、それを行うのは少し複雑だと思います。これらの統合テストのそれぞれは、達成するのが複雑なことになるでしょう!
一度にロット全体ではなく、シンボルテーブルの小さなビットをテストするアプローチをとろうと思います。Javaツリーが正しく構築されているかどうかをテストしている場合、次のようになります。
このアプローチは統合スタイルのテストですが、各統合テストはシステムのごく一部のみをテストします。
基本的に、私はテストをできるだけ小さく保つように努めます。ツリーのビットを引き出すためのテストコードの多くは、テストクラスを小さく保つためにユーティリティメソッドに移動できます。
シンボルテーブルを使用して、対応するソースファイルを出力するきれいなプリンターを作成できるかもしれないと思いました(すべてがうまくいけば、元のソースファイルと同じようになります)。問題は、元のファイルが私のきれいなプリンターが印刷するものとは異なる順序である可能性があることです。このアプローチでは、ワームの別の缶を開けているだけかもしれません。私はコードの一部を執拗にリファクタリングしてきましたが、バグが目立ち始めています。軌道に乗せるには、統合テストが本当に必要です。
それはまさに私が取ったアプローチです。しかし、私のシステムでは、ものの順序はあまり変わりません。基本的にJavaASTノードに応答してコードを出力するジェネレーターがありますが、ジェネレーターが再帰的に自分自身を呼び出すことができるという点で少し自由があります。たとえば、JavaIfステートメントのASTノードに応答して起動される'if'ジェネレーターは'if('を書き出すことができます。次に、他のジェネレーターに条件をレンダリングするように依頼し、次に'){'を書き込み、他のジェネレーターに書き込みを依頼します。本体を取り出して、「}」と書きます。