Erlangのミューテーションテストツールの作成経験

数週間前に、Clojureの突然変異テストについて聞いたことがあります。これはテストの品質をテストする方法で、ソースコードに小さな変更が加えられ、テストはこれに気づくかどうかを判断します。 たとえば、プログラムで条件「a> 1」が使用され、それを「a <1」に置き換えてもテスト結果が変わらない場合、テストは改善される可能性があります。



このようなツールは、コンパイラーによって生成された内部表現を処理するための幅広い機能を提供するため、Erlang向けに簡単に作成できると思いました。 しかし、それはそれほど単純ではありませんでした。 カットの下で、私が最終的に出会った問題と解決策を説明しました。



私が達成しようとした主なことは、ツールの使いやすさでした。 つまり、複雑な設定やEmacsのインストールは必要ありません。 私はダウンロードし、ローンチし、喜んだ。



次のシナリオを想定しました。コードに突然変異が導入され、テストが実行されます。 テストで突然変異が認識されなかった場合、レポート用にこの情報を理解して保存する必要があります。 困難が始まりました。



難易度1:テストの実行



テストを実行する最も一般的な方法は、シェルコマンドを使用することです。Erlangで運がよかったにも関わらず、リターンコードを取得するのは困難でした。この問題の例を次に示します。

erlang.org/pipermail/erlang-questions/2008-October/039176.html



つまり、外部ライブラリ(https://github.com/saleyn/erlexec)を接続する場合、gccトリッキーバージョンが必要であり、osxでテストされておらず、場合によってはtravisファイルで説明されているマジックが必要です。

github.com/saleyn/erlexec/blob/master/.travis.yml#L23



さて、それを外部ライブラリ、または最初にアーランベースのミューテーターを開始し、次にテスト自体を開始し、必要に応じてレポートを生成する追加のユーティリティにします。



難易度2:プリティプリンター



抽象化しすぎないようにミューテーターを書き始めました。jsxを使いました。このパッケージには多数の単体テストがあり、すべてが必要なようです。



Erlangで次のコードを作成しました。



{ok, Forms} = epp:parse_file("src/jsx_decoder.erl", []), io:format("~s", [erl_prettypr:format(erl_syntax:form_list(Forms))]).
      
      







私は構造が以下の事実に少し恥ずかしかったです。

 -compile({inline, [handle_event/3]}).
      
      





になった

 -compile({inline, [{handle_event, 3}]}).
      
      







パーサーは両方の方法を理解していることがわかります。



しかし、美的苦痛のほとんどは、コメントなしですべてのコードが再び推論されたという事実によって引き起こされ、初期バージョンと比較することは非常に困難でした。



コメントを保存するために、少し前に解析済みのコードを「キャッチ」しようとすることにしましたが、トークン化の直後に消えることがわかりました。



 1> erl_scan:string("a. % this is my atom"). {ok,[{atom,1,a},{dot,1}],1}
      
      







私の知る限り、これはマクロ置換の前に実行されるかなり低レベルの関数です。

 erl_scan:string("-define(A)."). {ok,[{'-',1}, {atom,1,define}, {'(',1}, {var,1,'A'}, {')',1}, {dot,1}], 1}
      
      







難易度3:マクロ



プリティプリンターの排気を見ると、その不在がわかります。 jsxの場合、「-ifdef(TEST)。」内にあるため、すべてのテストはファイル処理中に食べられました。



これはもちろん解決すべき問題です。正しい定義のセットをepp:parse_fileに渡すだけで十分でしたが、マクロを展開するとコードがきれいに印刷された直後よりも認識されにくくなりました。



解決策



数日間ためらった後、私はpythonでErlangのパーサーを作成しました。 最初に、元のアーラン文法を発見しました。

github.com/erlang/otp/blob/master/lib/stdlib/src/erl_parse.yrl



単純なタスクのように思えなかったため、1対1で転送しないことにしました。代わりに、メモリからアーラングラマーを作成し、いくつかの有名なプロジェクトでパーサーを駆動してデバッグしました。 AST構築IDは必要ありませんでしたが、これによりタスクが簡素化されました。 デバッグ中に、これらのリポジトリーで、例えばハックニーのような面白い場所を見つけました。

github.com/benoitc/hackney/blob/master/src/hackney.erl#L1025



 -define(METHOD_TPL(Method), Method(URL) -> hackney:request(Method, URL)). -include("hackney_methods.hrl"). -define(METHOD_TPL(Method), Method(URL, Headers) -> hackney:request(Method, URL, Headers)). -include("hackney_methods.hrl") ...     
      
      







または、たとえば、配線では、ほとんどの場合、beginとendの無意味な使用は次のとおりです。

github.com/rvirding/lfe/blob/develop/src/lfe_parse.erl#L238



 reduce(0, [__1|__Vs]) -> [ begin __1 end | __Vs]; reduce(1, [__1|__Vs]) -> [ begin value (__1) end | __Vs]; reduce(2, [__1|__Vs]) -> [ begin value (__1) end | __Vs]; reduce(3, [__1|__Vs]) -> [ begin value (__1) end | __Vs]; reduce(4, [__1|__Vs]) -> [ begin value (__1) end | __Vs]; reduce(5, [__1|__Vs]) -> [ begin make_fun (value (__1)) end | __Vs];
      
      







パーサーは、ここで説明しなかったすべての構造、つまり、覚えていない、または知らなかったすべての構造につまずきました。これはかなり興味深い経験でした。



パーサーの準備ができた後、変換(突然変異)の記述はすでに非常に簡単でした。



結果



  1. たくさんの楽しみ。
  2. BSDライセンスの下でのPython用Erlangパーサー。
  3. 書式設定を変更したりコメントを削除したりせずにコードを変更する変異ライブラリは、結果として、ソースコードに重ね合わせて変異コードを取得できる差分を生成します。




ライブラリコード:

github.com/parsifal-47/muterl



All Articles