uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

Grails/Groovyでのカバレッジ取得に関してのTIPS

プリミティブ最適化を抑制することでブランチカバレッジをましなものに

Grailsでは、コードカバレッジは以下で取得できる。

しかし、上記を使用した場合、分岐網羅(ブランチカバレッジ)は多くの場合、期待する値が取得できない。この理由の1つは、Groovy 1.8以降で導入されたプリミティブ最適化によって、型がプリミティブかどうかによっての条件分岐を行うコードがGroovyのコード生成器によってバイトコード上生成されているためである。Test Code Coverage Pluginはバイトコードレベルでカバレッジ情報を収集するので、ソース上に表われない暗黙の分岐をカバレッジ率の分母に計上してしまう。そしてその値は一般には100%にすることが困難である。

本来Groovyにおいて、プリミティブ最適化を抑止するためのコマンドラインオプション「--disableopt int」が存在する。しかし、GrailsではGroovyをコマンドラインから起動するわけではなくオプションが指定できない。

この問題の対処の一つは、この記事のリンク先にある、「renataogarcia/disableOptimizationsTransformation · GitHub」のコンパイル結果jarである
DisableOptimizationsTransformation-0.1-SNAPSHOT.jar」をダウンロードし、クラスパスに通すことである。こうすればGrails上でのGroovyの最適化が抑制されるため、ブランチカバレッジが正確に測定できる可能性がたかまる(ただし、ブランチカバレッジが期待する値にならない理由はこれだけが原因とは限らないことに注意*1 )。
なお上記jarをクラスパスに通す方法として以下が考えられる。

  1. 環境変数CLASSPATHに設定
  2. GRAILS_PROJECT/libs配下に置く
  3. jarをpom化してローカル/どこまのmaven repoに置き、testディペンデンシーに記述する

どの方法でも良いが、「試験では最適化抑制をし、プロダクトコードでは最適化する」ということはリスクになるため、テスト時に常に抑制するのは怖い気がする(プロダクトコードでも最適化抑制するなら別だが)。なので、テストのとき常に、ではなく、カバレッジ判定のときだけ一時的な指定をするという意味で1でも良いかもしれない。

なお、上記の設定前後でgrails cleanを実行した方がよい。

*1:具体的にはコントローラのためのAST変換などの適用結果として、分岐文が追加されているときがある。しかし、本ノウハウを適用してノイズを除去した状態にすれば、「ここには分岐はないはず」「ここにラインカバレッジ上は到達しているのに、ここに来てないはずはない」といった類推によって、ブランチカバレッジを目視確認で把握可能である場合も多くなる。経験上。

Grailsで、tomcatを起動するというだけの目的でファンクショナルテストを設定する方法

Grailsでコントローラの試験を、モックを使わず実際の通信を使って試験したいケースがある。たとえば、Rest APIを開発する場合、Rest APIはブラウザを使用しなくても簡単に呼び出せるものなので、ユニットテストがしたいです。もちろんGrailsにはコントローラをモックで試験する機能があるが、rest APIを呼び出せば済むものを、偽物ですます必然性があまりないと個人的には思う(もちろん試験実施時のtomcat立ち上げ下げのための実行時間を短縮したい、などの関心はありうるが)。

このためにはファンクショナルテストを実行する必要がある(unit/integrationではtomcatはたちあがらない設定になっている)。しかし、Grailsでは、ファンクショナルテストは単にtest/functional配下にテストコードを置くだけでは実行できず、一つなんでもいいからファンクショナルテスト用プラグインを導入しておく必要がある。

Gebの機能は特に使いたくないとしても、たとえばHttpClientなどでRESTの試験だけしたいという場合に、以下の手順でGebを形だけでも導入することで、ファンクショナルテストを行うことができるようになる。

(1) BuildConfig.groovyのdependenciesセクションに以下を追加

      dependencies {
               :
          test "org.gebish:geb-spock:0.10.0"
      }

(2) BuildConfig.groovyのpluginsセクションに以下を追加

      plugins {
                :
        test ":geb:0.10.0"
      }

(3) BuildConfig.groovyでfolkedモードをfalseに設定する

     grails.project.fork = [
          :
         test: false, // ←
         run: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
         war: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256, forkReserve:false],
         console: [maxMemory: 768, minMemory: 64, debug: false, maxPerm: 256]
     ]

(4) テストコードをGRAILS_PROJECT/test/functional配下に作成。

ここでのテストコードは、Gebの機能を使う必要はなく、HttpClientなどを使用した通常のSpockコードで良い(もちろんGeb使っても良い)。

(5) テスト実行

     $ grails test-app functional:

上記はbook of gebより。

(周知) 予告: JGGUG大忘年会LT大会と、LondonのG*なカンファレンス行ってきた報告!+合宿の報告もあるよ! #jggug

今週末は、JGGUG忘年会です。あしたビアバッシュのピザなどを予約しますんで、参加希望のかたは今日中にぜひどうぞ。

JGGUG大忘年会LT大会と、LondonのG*なカンファレンス行ってきた報告!+合宿の報告もあるよ! - 日本Grails/Groovyユーザーグループ | Doorkeeper
建物とフロアは同じですが、会議室が通常と違いますので注意を。

LTネタ考え中。