最近の個人的なプロジェクトのために、私は自分のプログラミング言語の通訳に取り組み始めました。このプロジェクトで自分で設定した基本ルールの1つは、開発するコードをできるだけ多く適切にテストする必要があるということです。
パーサーのテストに関するガイドやディスカッションを探してWebを検索しましたが、このトピックを真正面から扱っているリソースはほとんどありません。実際、私が見つけたいくつかのリソースは、本質的に、解析ケースをテストするための一種の「強引な」アプローチを示唆しており、最終的には次のように要約されます。
私がリストした3つのオプションのうち、3番目のオプションが最も好まれます。これは、テストコードを明示的なAST構造から切り離し、テストコードのバグが発生しにくいように見える(オペコードの配列の同等性をテストする)ためです。値は、ネストされたAST要素)の同等性を検証するよりもエラーが発生しにくいようです。
それでも、私はCでインタープリターを作成しており、バイトコードにリテラルを指定するのは面倒で冗長です。テストと期待される結果を指定するための、より簡潔でありながら同様に徹底的な方法を見つけることができるかもしれないと期待していました。
多くのテストフレームワークには、テストでのランダム化のサポートが組み込まれていることを知っています。数百または数千の決定論的な例を手作業で書くのではなく、何らかの形でいくつかの式の断片を指定して、それらをパーサーにランダムに送信できればと思います。ただし、この戦略では、テストスイートでもパーサーロジックを本質的に書き直すことなく、コンパイラ出力の正確さをどのように検証できるかは不明です。
パーサジェネレータのようなものを使用して、機能するパーサを生成できることはわかっています。私はこのプロジェクトを開始して、さまざまな部分がどのように機能し、連携するかを学びました。そこで、すべてを手書きで書くことにしました。
私はおそらく少なくとも2種類のテストを書くでしょう。既知のテキストを使用してテストを作成し、それを「ASTまたはエラー」と比較します。また、提供されたASTを取得し、実行を検証するテストを作成します(バイトコードエンジンを個別にテストして、「正しいバイトコードが生成されることを検証する」、または単に「既知の状態で実行し、結果の状態を検証する」)。
ASTをテストする方法はそれほど重要ではありません。シリアル化が決定論的である限り、構造の比較またはシリアル化のどちらでも問題なく機能するはずです。
実用的なアプローチは、多くのテスト例との統合テストを書くことです。
対応する出力(結果またはエラーメッセージ)を含む入力用の例の大きなリポジトリを作成します。
自動テストは例を繰り返し処理し、インタープリターの出力が期待される出力と一致することを確認します。
example: valid math calculation
source: "Sqrt(49)"
expected errormessage: none
expected output: "7"
example: error in math calculation
source: "sqrt(-1)"
expected errormessage: "Illegal Argument exception: Cannot calculate squareroot from negative number"
expected Java output: none
重複ではありませんが、密接に関連しています: BDDを使用してコンパイラーの単体テストを行う方法は?