CircleCIでIntelliJ IDEAのInspection機能による静的テストを実行する
この記事は、ソフトウェアテスト Advent Calendar 2018 の7日目の記事です。
ソフトウェアテスト Advent Calendar 2018
https://qiita.com/advent-calendar/2018/softwaretesting
CIツールで静的テストを実行し、
コードの品質を維持する取り組みを行っているプロジェクトは多いと思います。
静的テストのツールは、
checkstyle, PMD, JDepend, flake8, RuboCop, Brakeman, ESLintなど
いろいろとありますが。
IntelliJ IDEAのようなInspection機能があるIDEを導入していると、
IDEと静的テストツールの指摘に食い違いがあったり、
レビュアーが修正を手元のIDEに取り込んでチェックする手間が出てきたり、
など、面倒な事が出てきがちです。
そこで、このエントリでは、
IntelliJ IDEAのInspection機能をCircleCI上で実行する方法を説明します。
以下のように、
IntelliJ IDEAはInspection機能をコマンドラインで実行する事が出来るので、
この機能を活用します。
Running Inspections Offline | Intellij IDEA Help
https://www.jetbrains.com/help/idea/running-inspections-offline.html
適当なJavaのプロジェクトを用意する
まずは、適当なJavaのプロジェクトを用意します。
以下のようにディレクトリを準備します。
$ mkdir java-sample
$ cd java-sample
$ mkdir -p src/main/java
以下のファイルを作成します。
build.gradle
plugins {
id 'java'
id 'application'
}
mainClassName = 'Hello'
src/main/java/Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello");
}
}
gradleから実行出来ることを確認します。
$ gradle run
プロジェクトをIntelliJ IDEAにimportする
次に、作成したプロジェクトをIntelliJ IDEAにインポートします。
IntelliJ IDEA起動後、Import Projectを選んだ後。
先ほど作成したディレクトを指定、
Import project from external modelで、Gradleを選択します。
File -> Project Structure -> Projectとたどり、
Project SDKを指定します。
ここでは、Java 10を利用するので「10」を選択しておきます。
Inspectionsを設定してGitHubにpushする
Preferences -> Editor -> Inspectionsとたどります。
ProfileをStore in IDE - Defaultに切り替えた後、
右側の歯車メニューからCopy to Projectを選択し、
Profile名をDefaultに指定します。
この後、必要に応じてInspectionの設定は変更して下さい。
ここで、必要なファイルをGitHubにpushします。
gitignoreを以下のように、指定して置きます。
.gitignore
.gradle
build
.idea/*
!.idea/gradle.xml
!.idea/misc.xml
!.idea/inspectionProfiles
commitするファイルは以下のようになります。
- build.gradle
- src/main/java/Hello.java
- .idea/gradle.xml
- .idea/misc.xml
- .idea/inspectionProfiles
- .gitignore
これらのファイルを追加したら、GitHubにpushします。
DockerでInspectionを実行する
ここまでの流れで、
プロジェクトとInspectionルールの設定がGitHub上で共有されました。
ここでは、
InspectionをCircleCIで実行するためのDockerコンテナを用意します。
私が作成したDockerイメージをDockerHubに置いているので参考にして下さい。
元になったDockerfileもGitHubに上げています。
takemikami/ideaic-inspect | DockerHub
https://hub.docker.com/r/takemikami/ideaic-inspect
takemikami/docker-ideaic-inspect | GitHub
https://github.com/takemikami/docker-ideaic-inspect
※Java 10用のDockerfileは「10」ブランチにあります。
https://github.com/takemikami/docker-ideaic-inspect/tree/10
このイメージ(あるいは参考にして自分で作ったイメージ)を使って、
以下のように、コマンドラインからinspectionが実行出来ることを確認します。
docker run -i -v `pwd`:/project -v $HOME/.gradle:/root/.gradle -t takemikami/ideaic-inspect:10 /bin/bash
# cd /project
# mkdir -p report
# /opt/idea-IC/bin/inspect.sh `pwd`/build.gradle `pwd`/.idea/inspectionProfiles/Default.xml `pwd`/report -v2
結果は、report配下にxml形式で格納されます。
CircleCIでInspectionを実行する
CircleCIのサイトで、
ADD PROJECTS から対象プロジェクトを選び Set Up Project でCIを有効にします。
以下のような、CircleCIの設定ファイルを作成してpushします。
.circleci/config.yml
version: 2
jobs:
build:
docker:
- image: takemikami/ideaic-inspect:10
steps:
- checkout
- run:
name: inspect
command: |
mkdir report
/opt/idea-IC/bin/inspect.sh `pwd`/build.gradle `pwd`/.idea/inspectionProfiles/Default.xml `pwd`/report -v2
この状態では、CiecleCIの画面からinspection結果を確認出来ないので、
inspectionの結果を標準出力に表示するシェルスクリプトを追加します。
.circleci/print-inspect.sh
#!/bin/bash
PROJECT_ROOT=$(cd $(dirname $0)/..; pwd)
ISSUE_CNT=0
for target_xml in `find $PROJECT_ROOT/report | grep xml | grep -v '/\.'`; do
echo $target_xml
problem_count=1
while :
do
problem_xml=`echo "cat /problems/problem[${problem_count}]" | xmllint --shell $target_xml`
if [ "${problem_xml}" = "/ > / > " ]; then
break
fi
filename=`echo ${problem_xml} | sed -e "s/^.*<file>\([^<]*\).*$/\1/"`
line=`echo ${problem_xml} | sed -e "s/^.*<line>\([^<]*\).*$/\1/"`
description=`echo ${problem_xml} | sed -e "s/^.*<description>\([^<]*\).*$/\1/"`
severity=`echo ${problem_xml} | sed -e "s/^.*severity=\"\([^\"]*\)\".*$/\1/"`
source=`echo ${problem_xml} | sed -e "s/^.*<problem_class.*\">\([^<]*\).*$/\1/"`
filename_regex="^file://"
if [[ $filename =~ $filename_regex ]]; then
filename=${filename:21} # ファイル名先頭の「file://$PROJECT_DIR$」を除く
fi
line_regex="^[0-9]*$"
if [[ ! $line =~ $line_regex ]]; then
line=""
fi
echo "$filename:$line [$severity] $description ($source)"
if [ "$severity" != "TYPO" ]; then
ISSUE_CNT=$((ISSUE_CNT + 1))
fi
problem_count=$((problem_count + 1))
done
done
echo "$ISSUE_CNT issues."
if [ $ISSUE_CNT -gt 0 ]; then
exit 1
fi
exit 0
実行権限も忘れずに。
$ chmod +x .circleci/print-inspect.sh
CircleCIの設定もシェルスクリプトを呼び出すように変更します。
.circleci/config.yml
version: 2
jobs:
build:
docker:
- image: takemikami/ideaic-inspect:10
steps:
- run:
name: install xmllint
command: apt-get update && apt-get install -y libxml2-utils
- checkout
- run:
name: inspect
command: |
mkdir report
/opt/idea-IC/bin/inspect.sh `pwd`/build.gradle `pwd`/.idea/inspectionProfiles/Default.xml `pwd`/report -v2
.circleci/print-inspect.sh
以下のファイルをGiHubにpushしてPullRequstを上げます。
- .circleci/config.yml
- .circleci/print-inspect.sh
以下のように、
CircleCIでのCIに失敗し、PullRequestでも状態が確認出来ます。
ルールが厳しすぎる場合は、IDE上からInspectionルールの設定を変更して、 .idea/inspectionProfiles 以下をGitHubにpushします。
inspection結果の表示を、
PullRequestで修正した箇所のみに絞り込みたい場合は、
以下のエントリの方法を真似れば、対応することが出来ます。
RubocopでPullRequestの変更箇所で問題があった場合のみCIを失敗させる http://takemikami.com/2018/01/30/RubocopPullRequestCI.html
このようなプロセスを整備して、
機械に出来るテストは、どんどん機械にやらせていきましょう。
今後、このようなCIで使うと便利なシェルスクリプト群をまとめていこうと思っています。
現在作業中ですが、以下のリポジトリにまとめていく予定です。
https://github.com/takemikami/ci-utils