循環的複雑度を測定して、生産性をBoostしよう!(Java)


本記事では、

  • プロジェクトのリスク患部にすぐ効き
  • すばやくテスト実行ができ
  • 本当に重要なことに焦点をあてることができ
  • アプリケーションコードとテストコードのバランスを保つことができ
  • CIに組み込むことができ
  • IDEで使えて生産性を爆上げできる

といったツールを紹介します。

それがOpenCloverというツールです。

OpenCloverとは

高機能なJavaカバレッジ計測ツールの1つです。
もともとはAtlassianという会社の製品だったのですが、2017年にOSSになりました。以降も開発は続けられているようですが、現在(2020/12時点)の最新版のリリースは2019年10月のものとなっています。

特徴

対応言語

Java, Groovy, AspectJに対応しています。

値段

OSSなのでもちろん無料です。

コードカバレッジの種類

3種類のカバレッジを提供しています。

  • method coverage
    • テスト実行時に使用されたメソッドの計測
  • statement coverage
    • テスト実行時に通ったコードの計測
  • branch coverage
    • テスト実行時のコードの分岐(if文とか)の計測

コードメトリクスの種類

20種類以上のメトリクス計測を行うことができます。ここではいくつか主要なものを紹介したいと思います。

  • complexity
    • 循環的複雑度の計測
  • avgMethodComplexity
    • 1メソッドあたりの分岐数による複雑度の平均
  • complexityDensity
    • 1文あたりの循環的複雑度(=循環的複雑度の密度)
  • Branches
    • 分岐の数
  • Statements
    • コード実行行
  • Total Coverage
    • テストによってカバーされた割合
  • ncLineCount
    • コメント行を除いた行数
  • lineCount
    • 行数

循環的複雑度については、ググるとわかりやすい説明が出てくるのでそちらを参照してみてください。Qiitaにもいくつか記事が上がっています。

例:

public class Complexity {

    public String helloWorld() {
        return "Hello world";
    }

    public String greet(boolean isMorning) {
        if (isMorning) {
            return "Good Morning";
        } else {
            return "Hello";
        }
    }
}
public class ComplexityTest {

    @Test
    public void returnHelloWorld() {
        Complexity target = new Complexity();
    String actual = target.helloWorld();
        assertEquals("Hello world", actual);
    }

    @Test
    public void returnGoodMorning() {
        Complexity target = new Complexity();
        String actual = target.greet(true);
        assertEquals("Good Morning", actual);
    }
}

以上のようなコードで単体テストを実行すると、


以上のような結果が得られます。
helloWorld()は分岐なしで循環的複雑度が1、greet(boolean isMorning)はif elseで循環的複雑度が2なので、1クラスあたりの循環的複雑度の合計が3、平均が1.5となります。
また、カバレッジ率もファイル、クラス、メソッドごとに割合が表示されます。

public String numToString(int num) {
        if (num == 1) {
            return "1";
        } else if (num == 2) {
            return "2";
        } else if (num == 3) {
            return "3";
        } else if (num == 4) {
            return "4";
        } else if (num == 5) {
            return "5";
        } else {
            return "not supported";
        }
    }

以上のようなコードを追加してテストを実行してみると、


カバレッジ率や循環的複雑度の合計・平均、分岐の数などが再計算されます。

対応ツール

ビルドツール

  • Ant
  • Maven
  • Gradle
  • Grails
  • Griffon

CIツール

  • Jenkins
  • Hudson
  • Bamboo

IDE

  • Eclipse
  • IntelliJ IDEA

その他

  • Sonar
  • JIRA

テストツール

  • JUnit 3 & 4
  • TestNG
  • Spock

レポートファイル

以下のファイルに計測結果を出力できます。

  • HTML
  • XML
  • PDF
  • JSON
  • TEXT

使用例(Eclipse)

導入手順

↑に書いてあるとおりにプラグインを導入していきます。

使用しているeclipseのバージョンは2019-03です。

  1. 上部メニューのHelp → Install new softwareを選択
  2. `Addボタンをクリック、Name:のテキストボックスには適当に何か名前を入れて、Location:http://openclover.org/update を入れて、Addボタンをクリック
  3. Clover 4が表示されるのでチェックボックスにチェックする
  4. Finishボタンをクリック
  5. ライセンスに同意して、再起動が促されるのでeclipseを再起動

使用方法

↑のコードを使用しています。

  1. プロジェクトを選択して右クリック
  2. Clover → Enable on this Projectをクリック (Enable/Disable onを選択すると、複数のプロジェクトでCloverの使用可否を設定できる) (このときメトリクス測定コードを対象のプロジェクトコードに埋め込み、別のファイルとしてビルドするため、プロジェクトが巨大な場合は時間がかかる)
  3. Cloverがenableになったプロジェクトで単体テストを実行すると、カバレッジ、メトリクスの測定がされます

テスト実行後


実行したテストでカバーされたコードの背景色は緑色、カバーされなかったコードは赤色になります。
左側には表示される数字は、実行したテストによってコードが通った回数になります。

各タブについて

Coverage Explorer


各クラス/メソッドのテストカバレッジ率、メソッドの平均複雑度と複雑度が表示されます。

このタブがCloverのHome画面のようなもので、右上のアイコンでいろいろな操作が可能です。

  • Cloverのenbale/disabel
  • カバレッジの更新、カバレッジ記録の削除
  • レポートの出力
  • プロジェクトの表示
  • カバレッジの背景色の表示

などの操作ができます。

Test Explorer


実行したテストのリスト、いつ実行したか、テスト結果、テスト実行時間、テスト実行時のメッセージが表示されます。
ほとんどJUnitのタブと差はありませんが、こちらのほうが見やすいと思います。

Test Contribution


エディターで開いているファイルのコードカバーに貢献(直訳)しているテストが左側に、カーソル位置を通っているテストが右側に表示されます

Clover Dashboard


文字通りダッシュボードです。カバレッジ、テスト結果、循環的複雑度の高いパッケージ、クラス、プロジェクト内でリスクの高い(=複雑度が高い)クラス、最新のテストしたメソッドの概要が表示され、その詳細画面へと遷移することができます。

レポート内容

Dashboard


コード分析結果の概要やグラフが表示されます。See moreのやパッケージ/クラス/メソッドのリンクをクリックすると、それぞれのタブページに遷移します。

Application code




プロジェクト、パッケージレベルのコードメトリクスの概要が表示されます。また、各ソースファイルのメトリクス、カバレッジ結果も閲覧することが可能です。

Test code

Application Codeタブの内容と同様です。テストファイルのコードメトリクス、カバレッジ結果を閲覧できます。

Test results

実行されたテストに関する情報が表示されます。テスト結果や期間、テスト対象となったクラスのリストなど。

Top risks

Openclover特有のレポート結果の1つです。
コードのカバレッジ率が低→高になるにつれ、色が赤→緑となっています。
またメソッドの複雑度が高いクラスほど大きい文字で表示されます。
クラス名が赤色に近く大きいフォントサイズであるほど、コードがテストされておらず複雑なクラスになっている(=リスクが高い)ということになります。直感的でわかりやすいですね。

Quick wins


これもOpenclover特有のレポート結果の1つです。
Quick winsという名の通り、簡単に勝てる、つまり「これをテストすれば簡単に全体的なカバレッジがあがる」というクラスを示してくれています。
("low hanging coverage fruit" = 低いところに吊るされたカバレッジフルーツ = 楽に手に入れる、達成することができるカバレッジ目標)
テストされていないコードや分岐が多→少になるにつれ、赤→緑となっています。
またコードや分岐の数が多いクラスほど大きい文字で表示されます。
クラス名が赤色に近く大きいフォントサイズであるものからテストを達成することで、全体的なテストの達成率をあげることができるということになります。
Top risksと文字色、大きさの指標が違うところに注意しましょう。

Coverage tree map


これも面白い分析結果です。
テストしたプロジェクトのカバレッジ率と複雑度がマップ上に可視化されたものになります。
マップ上の領域が広く赤いほど、複雑度が高くテストによってカバーされていないクラスになるので、その部分をクリックしてどんなコード内容になっているのかを確認するといった使い方ができるかと思います。
規模が大きめのプロジェクトでマップすべてが緑色で埋め尽くされたら、きっと壮観なマップになることでしょう。

本ツールの使い所

例えば、テストコートがあまり充実していない、そもそもテストコードがないというようなレガシープロジェクトにおいて、このツールは強力な効果を発揮するでしょう。
そういったレガシープロジェクトでは、テストを追加しよう、リファクタリングをしようという方針が決まっても、どこから手をつけたらいいかわからないといったケースが多々あるかと思います。
そこでこのツールを使ってレポート出力をしてみて、どこのクラスが循環的複雑度が高いのかなどの内容をチーム内で共有し、テストを追加、リファクタしていくクラスを決定するといった計画が立てやすくなると思います。

また、そもそも単体テストを追加していくことやリファクタリングをすることに懐疑的な方々に対しての説得材料として、レポートを使うことができるかもしれません。
「現状のプロダクトはこんなにコードが複雑になっていて、これ以上機能追加をしていくとバグの混入率も高くなるし、機能追加にも時間がかかります」といった内容を数値と一緒に提示することができれば、コードの改善に割ける時間をGETしやすくなるでしょう。

テストコードが充実しているプロジェクトでも、機能追加した際のテストの抜け漏れを確認しやすいと思います。

あるいは、新人教育のツールとしても一役買うかもしれません。
私は新人のときに課題でデモアプリを作成しました。そのときに先輩社員から「1メソッドあたり多くても10~20行になるように作ってみるといいよ」と目標を提示されました。
処理を分割して可読性の良いコードを書く、といった意図があったと思われますが、たとえ1メソッドあたり10行前後で書かれていてもif文がnestしまくっていたり論理演算子が多かったりしていたら、行数は短くても読みやすいとは言えません。
そこでこのツールを用いてコードの複雑度を計測して、その数値を10以下に抑えるといった目標でコードを書いてもらえば、より可読性の良いコードを書く練習になると思います(循環的複雑度が10以下だと非常に良い構造だと言われています)。
また、自分で書いたコードが客観的に見て良いコードなのかというのを数値で判断することができ、そこからコードの改善意識も高まるかもしれません。

豊富な分析結果がレポートとしてきれいに可視化されるので、大規模なプロジェクトになるほど、使い勝手のよいコード共有ツールとして使用できるかと思います。

まとめ

導入も簡単にできるし、使い方も難しくなく、レポートも直感的でわかりやすく読みやすい。そして無料!
とりあえず入れておいて損はないと思います。上手く使えれば謳い文句にもある通り、生産性を大幅にブーストすることも可能でしょう。
Javaで開発をしている方々、ぜひ1度試してみてはいかがでしょうか?