ScalaプロジェクトをCircleCIでビルドしてカバレッジをとってみた
カバレッジ100%とか馬鹿なことは言いませんが、テスト書いてなさすぎるとかないよね、という指標にカバレッジを使う価値は十分にあります。テスト自体は流せているので、せっかくなのでカバレッジも取得しましょうか。自分のプロダクトで取るの慣れておきたいところですし。
カバレッジの計測
先日の続き。下記エントリを引き続き参考にします。
sbt-scoverage
プラグイン
Scalaプロジェクトでカバレッジを計測するためのプラグインです。scoverageというカバレッジ計測ツールをsbtプラグインとして利用可能になるようにします。上記エントリは参照しているプラグインのバージョンがちょっと古いので公式を当たってみます。
上記エントリの時はカバレッジ用にデータを埋め込む形でコンパイルしないといけなかったみたいですが、今はそうでもないみたいですね。リゾルバを追加する必要もないみたいです。プラグイン追加後に sbt clean coverage test
を実行します。
$ sbt clean coverage test [info] Loading project definition from /Users/shinsuke-abe/IdeaProjects/Thoth/project [info] Set current project to Thoth (in build file:/Users/shinsuke-abe/IdeaProjects/Thoth/) [success] Total time: 0 s, completed 2015/10/19 13:35:49 [info] Set current project to Thoth (in build file:/Users/shinsuke-abe/IdeaProjects/Thoth/) [info] Updating {file:/Users/shinsuke-abe/IdeaProjects/Thoth/}thoth... [info] Resolving jline#jline;2.12.1 ... [info] Done updating. [info] Compiling 9 Scala sources to /Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/classes... [info] [info] Cleaning datadir [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-data] [info] [info] Beginning coverage instrumentation [info] [info] Instrumentation completed [253 statements] [info] [info] Wrote instrumentation file [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-data/scoverage.coverage.xml] [info] [info] Will write measurement data to [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-data] 〜〜中略〜〜 [info] Reading scoverage instrumentation [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-data/scoverage.coverage.xml] [info] Reading scoverage measurements... [info] Generating scoverage reports... [info] Written Cobertura report [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/coverage-report/cobertura.xml] [info] Written XML coverage report [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-report/scoverage.xml] [info] Written HTML coverage report [/Users/shinsuke-abe/IdeaProjects/Thoth/target/scala-2.11/scoverage-report/index.html] [info] Coverage reports completed [info] All done. Coverage was [71.54%] [info] Passed: Total 32, Failed 0, Errors 0, Passed 32 [success] Total time: 32 s, completed 2015/10/19 13:36:21
テストも通りましたし、標準出力にカバレッジ計測結果も出ています。レポートもデフォルトで複数の形式で出してくれています。ほぼ個別の設定なしでこれだけ出してくれれば十分ですね。71.54%はちょっと予想外でしたが、内容見てまあ納得。。。
次はこれをCircleCIで計測して出力したArtifactにレポートします。
general: artifacts: - "target/scala-2.11/coverage-report" dependencies: cache_directories: - graphviz-2.38.0 - "~/.ivy2" - "~/.sbt" pre: - wget -q https://dl.bintray.com/sbt/debian/sbt-0.13.8.deb - sudo dpkg -i sbt-0.13.8.deb - wget -O graphviz.tar.gz --quiet http://www.graphviz.org/pub/graphviz/ARCHIVE/graphviz-2.38.0.tar.gz - tar -zxf graphviz.tar.gz - graphviz-2.38.0/configure --silent - make --silent --ignore-errors && make --silent --ignore-errors install > /dev/null - echo 'which dot && version:' - which dot - dot -V - sudo apt-get -y install pandoc test: override: - "sbt clean coverage test"
カバレッジレポートが出力されるディレクトリを general:artifacts
に指定して、テスト実行コマンドを、 sbt-scoverage
で指定されているコマンドに上書きします。プッシュしたあとにArtifactを見てみましょうか。
テスト時の標準出力にhtml形式の出力があるのにArtifactsには covertura.xml
しかいない。。。どゆこと?まあ、どうせCoverallsに送るつもりだし、人間が生でみたければローカルで出せばいいので放っておきます。
sbt-coveralls
プラグイン
これらの計測結果、テストを流した時だけではなくブランチやコミットごとの変化なんかを見たいですよね。カバレッジ下がってきつつあるから気をつけよう、とか。CircleCIのArtifactをテストごとに見てもいいんでしょうけど、どうせならそれに特化したサービスがあれば、ということでCoverallsというサービスを使ってみます。
これは、GithubやBitBucketのリポジトリのコミットに対してカバレッジの計測結果を時系列、ブランチ別で整理してくれるサービスです。パブリックリポジトリだったら無料で使うことができます。時系列でまとめてくれるだけでなく、プルリクエスト作成時のカバレッジ閾値なんかも設定可能です *1 。
というわけでこれらの計測結果をCoverallsに送るための設定をします。
環境変数 COVERALLS_REPO_TOKEN
をCircleCIのプロジェクトにセットしましょう。トークンはCoverallsでプロジェクトの設定を行ったあとにプロジェクトのトップに表示されます。下図の service_name
の下に repo_token
という設定があってそこにしれっと書かれています。マニュアルっぽくしれっと書いてあるので最初完全にスルーしてました *2 。
circle.yml
を以下のように追記します。
general: artifacts: - "target/scala-2.11/coverage-report" dependencies: cache_directories: - graphviz-2.38.0 - "~/.ivy2" - "~/.sbt" pre: - wget -q https://dl.bintray.com/sbt/debian/sbt-0.13.8.deb - sudo dpkg -i sbt-0.13.8.deb - wget -O graphviz.tar.gz --quiet http://www.graphviz.org/pub/graphviz/ARCHIVE/graphviz-2.38.0.tar.gz - tar -zxf graphviz.tar.gz - graphviz-2.38.0/configure --silent - make --silent --ignore-errors && make --silent --ignore-errors install > /dev/null - echo 'which dot && version:' - which dot - dot -V - sudo apt-get -y install pandoc test: override: - "sbt clean coverage test" post: - "sbt coveralls"
テスト実行後に sbt coveralls
を実行するように指定するだけです。のはずなんですが、こけました。
[info] Loading project definition from /home/ubuntu/Thoth/project [info] Set current project to Thoth (in build file:/home/ubuntu/Thoth/) SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. [info] Repository = ./.git java.io.FileNotFoundException: /home/ubuntu/Thoth/markdown/ThothCustomMarkdownParser.scala (No such file or directory) at java.io.FileInputStream.open(Native Method) at java.io.FileInputStream.<init>(FileInputStream.java:146) at scala.io.Source$.fromFile(Source.scala:90) at scala.io.Source$.fromFile(Source.scala:75) at scala.io.Source$.fromFile(Source.scala:53) at org.scoverage.coveralls.CoberturaReader.reportForSource(CoberturaReader.scala:34) at org.scoverage.coveralls.CoverallsPlugin$$anonfun$doCoveralls$5.apply(CoverallsPlugin.scala:91) at org.scoverage.coveralls.CoverallsPlugin$$anonfun$doCoveralls$5.apply(CoverallsPlugin.scala:90) at scala.collection.immutable.HashSet$HashSet1.foreach(HashSet.scala:153) at scala.collection.immutable.HashSet$HashTrieSet.foreach(HashSet.scala:306) at org.scoverage.coveralls.CoverallsPlugin$.doCoveralls(CoverallsPlugin.scala:90) at org.scoverage.coveralls.CoverallsPlugin$$anonfun$coverallsCommand$1.apply(CoverallsPlugin.scala:28) at org.scoverage.coveralls.CoverallsPlugin$$anonfun$coverallsCommand$1.apply(CoverallsPlugin.scala:28) at sbt.Command$$anonfun$command$1$$anonfun$apply$1.apply(Command.scala:29) at sbt.Command$$anonfun$command$1$$anonfun$apply$1.apply(Command.scala:29) at sbt.Command$.process(Command.scala:92) at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:98) at sbt.MainLoop$$anonfun$1$$anonfun$apply$1.apply(MainLoop.scala:98) at sbt.State$$anon$1.process(State.scala:184) at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:98) at sbt.MainLoop$$anonfun$1.apply(MainLoop.scala:98) at sbt.ErrorHandling$.wideConvert(ErrorHandling.scala:17) at sbt.MainLoop$.next(MainLoop.scala:98) at sbt.MainLoop$.run(MainLoop.scala:91) at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:70) at sbt.MainLoop$$anonfun$runWithNewLog$1.apply(MainLoop.scala:65) at sbt.Using.apply(Using.scala:24) at sbt.MainLoop$.runWithNewLog(MainLoop.scala:65) at sbt.MainLoop$.runAndClearLast(MainLoop.scala:48) at sbt.MainLoop$.runLoggedLoop(MainLoop.scala:32) at sbt.MainLoop$.runLogged(MainLoop.scala:24) at sbt.StandardMain$.runManaged(Main.scala:53) at sbt.xMain.run(Main.scala:28) at xsbt.boot.Launch$$anonfun$run$1.apply(Launch.scala:109) at xsbt.boot.Launch$.withContextLoader(Launch.scala:128) at xsbt.boot.Launch$.run(Launch.scala:109) at xsbt.boot.Launch$$anonfun$apply$1.apply(Launch.scala:35) at xsbt.boot.Launch$.launch(Launch.scala:117) at xsbt.boot.Launch$.apply(Launch.scala:18) at xsbt.boot.Boot$.runImpl(Boot.scala:41) at xsbt.boot.Boot$.main(Boot.scala:17) at xsbt.boot.Boot.main(Boot.scala) [error] java.io.FileNotFoundException: /home/ubuntu/Thoth/markdown/ThothCustomMarkdownParser.scala (No such file or directory)
対応するソースの参照でパスを正しく読み切れずに落ちている模様。 markdown
の前に src/main/scala-2.11
が入らないと正しいパスではありません。cobertura.xml
の中にはソースのディレクトリがちゃんと出力されているので、scoverageが問題なわけではなさそうです。
プラグインのソース確認してみると、ソースのディレクトリは (sourceDirectories in Compile).gimme
ってところから取得しているようですね。ソースファイル名の配列を生成する時にexistsで確認しているので、存在するファイルしかこの配列にないはずなのですが。。。
って、リリースしろよ。。。
Githubリポジトリからプラグインのソースを引っこ抜いて使う
こうやって踏み抜いて本題から離れたことをやらないといけないのが非常に私らしいところですね。ってか、これ結構ビルドに時間かかるようになってアレなんですが。。。
http://qiita.com/kawachi/items/71af20a102ecca41561d
を参考にしてみます。
logLevel := Level.Warn addSbtPlugin("se.marcuslonnberg" % "sbt-docker" % "1.2.0") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.3") lazy val root = project.in(file(".")).dependsOn(githubRepo) lazy val githubRepo = uri("git://github.com/scoverage/sbt-coveralls.git")
とりあえずmasterブランチが欲しいので、コミットを指定していません。ローカルで動かしてcoverallsに飛ばすのは問題ありませんでした。プッシュしてみます。
まあ、見てください、9つしかテスト対象のないプロジェクトのビルドが10分オーバーですよ。そのほとんどが足回りですが。これは、プラグインとかライブラリとかはキャッシュが効くようにしないとまずいかもしれない。
というわけでようやくcoverallsに結果を飛ばせました。ご査収ください。