Lombokを使っているときにJacocoのカバレッジから自動生成分を除外する方法

## 概要

このエントリでは、ソースコードを生成してくれる[Lombok](https://projectlombok.org)を使っている前提で、カバレッジ計測ツールの[Jacoco](https://www.jacoco.org/jacoco/)でのカバレッジを上げていく方法について、サンプルを用意して紹介します。

このエントリの目的は、Lombokを使ってなるべくコードを書かないで、かつJacocoでのカバレッジを上げていくときの、とっかかりの参考となることを目的としています。それぞれのツールの全ての機能を解説するものではありません。

### 結論

結論から言うと、Jacoco0.8.0(2018/1/2リリース)以上を使って、lombok側で「lombok.addLombokGeneratedAnnotation = true」を指定することで、自動生成されたコードをカバレッジ検査対象から除外しましょう、というのが大きなポイントです。

### 参考エントリ

Qiita内では、Lombok, Jacocoについて、

- [Qiitaで探す](https://qiita.com/search?utf8=✓&sort=&q=lombok)と、347個ありました。

- [Qiitaで探す](https://qiita.com/search?utf8=✓&sort=&q=jacoco)と、79個ありました。

たくさん(2018/5/26時点)ありますが、下記が参考になります。

- [Jacoco 0.8.0でJavaコードカバレッジをより快適に](https://qiita.com/yoichiwo7/items/7f115487b3bed8196de9)
- 

## 初期サンプル

サンプルとして、Spring Bootで、REST Controller越しに簡単なエンティティを格納したり表示したりするものを作りました。

こんな感じの動作をするものです。

```bash
(登録)
$ curl -H 'Content-Type:application/json' -d '{"intField":123}' http://localhost:8080

(参照)
$ curl http://localhost:8080/ 
[{"id":"2815cfc6-f908-4c7d-a20b-b2c52953555f","intField":123,"longField":0,"stringField":null,"charField"

(1件詳細)
$ curl http://localhost:8080/2815cfc6-f908-4c7d-a20b-b2c52953555f
{"id":"2815cfc6-f908-4c7d-a20b-b2c52953555f","intField":123,"longField":0,"stringField":null,"charField":"\u0000","byteField":0}hirokis-M

```

[初期状態のリポジトリ](https://github.com/hrkt/lombok-jacoco-sample/releases/tag/v0.1)に格納しています。

この版では、lombokのお便利機能を使わず実装しています。

```bash
$ ./gradlew test
$ ./gradlew jacocoTestReport
```

カバレッジはこんな感じです。
<img width="512" alt="スクリーンショット 2018-05-26 14.39.51.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/988132fc-3e08-4fc1-1e5b-840fe9b2b8b0.png">

## lombok適用

### @ Data

<details><summary>適用前のSampleEntity</summary><div>

```Java:SampleEntity.java
public class SampleEntity {
    private SampleId id;
    private int intField;
    private long longField;
    private String stringField;
    private char charField;
    private byte byteField;
    /**
     * @return the id
     */
    public SampleId getId() {
        return id;
    }
    /**
     * @param id the id to set
     */
    public void setId(SampleId id) {
        this.id = id;
    }
    /**
     * @return the intField
     */
    public int getIntField() {
        return intField;
    }
    /**
     * @param intField the intField to set
     */
    public void setIntField(int intField) {
        this.intField = intField;
    }
    /**
     * @return the longField
     */
    public long getLongField() {
        return longField;
    }
    /**
     * @param longField the longField to set
     */
    public void setLongField(long longField) {
        this.longField = longField;
    }
    /**
     * @return the stringField
     */
    public String getStringField() {
        return stringField;
    }
    /**
     * @param stringField the stringField to set
     */
    public void setStringField(String stringField) {
        this.stringField = stringField;
    }
    /**
     * @return the charField
     */
    public char getCharField() {
        return charField;
    }
    /**
     * @param charField the charField to set
     */
    public void setCharField(char charField) {
        this.charField = charField;
    }
    /**
     * @return the byteField
     */
    public byte getByteField() {
        return byteField;
    }
    /**
     * @param byteField the byteField to set
     */
    public void setByteField(byte byteField) {
        this.byteField = byteField;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + byteField;
        result = prime * result + charField;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + intField;
        result = prime * result + (int) (longField ^ (longField >>> 32));
        result = prime * result
                + ((stringField == null) ? 0 : stringField.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        SampleEntity other = (SampleEntity) obj;
        if (byteField != other.byteField)
            return false;
        if (charField != other.charField)
            return false;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        if (intField != other.intField)
            return false;
        if (longField != other.longField)
            return false;
        if (stringField == null) {
            if (other.stringField != null)
                return false;
        } else if (!stringField.equals(other.stringField))
            return false;
        return true;
    }
    /**
     * @param id
     * @param intField
     * @param longField
     * @param stringField
     * @param charField
     * @param byteField
     */
    public SampleEntity(SampleId id, int intField, long longField,
            String stringField, char charField, byte byteField) {
        super();
        this.id = id;
        this.intField = intField;
        this.longField = longField;
        this.stringField = stringField;
        this.charField = charField;
        this.byteField = byteField;
    }

    public SampleEntity() {
        super();
    };

}
```
</div></details>



<details><summary>こうなります。</summary><div>
```Java:SampleEntity.java
@Data
public class SampleEntity {
    private SampleId id;
    private int intField;
    private long longField;
    private String stringField;
    private char charField;
    private byte byteField;
}
```
</div></details>

lombokプラグインが適用されたIDE(このエントリではEclipse)で確認すると、setter, getter, toString, hashCodeが生成されていることが確認できます。

<img width="100" alt="スクリーンショット 2018-05-26 14.53.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/1da2c50a-d8b1-3266-c75a-2b053ba4c708.png">

### @ RequiredArgsConstructor

Springの@Autowiredでフィールドをインジェクションしていると、テストなどので直接差し替えが面倒になったり、使っているものが多くなってきたときに場所をとったりということがあるので、コンストラクタでインジェクションする形に変えてみます。

<details><summary>適用前のSampleApi.javaが</summary><div>

```Java:SampleApi.java
@RestController
@RequestMapping("/")
public class SampleApi {
    @Autowired
    private SampleFacade sampleFacade;
    
```
</div></details>


<details><summary>こうなります。</summary><div>
フィールドがfinalになっているところに注意です。

```Java:SampleApi.java
@RestController
@RequestMapping("/")
@RequiredArgsConstructor
public class SampleApi {
    private final SampleFacade sampleFacade;    
```
</div></details>

ここまでで、jacocoReportはこんな感じになります。

<img width="512" alt="スクリーンショット 2018-05-26 15.10.35.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/1fea42b1-751f-1be9-6462-a5729a710981.png">

ここで、domainパッケージが結構な割合になっているわけですが、中をみれば当然ながらlombokで自動生成されたコードなわけです。lombokの動作を正とする時、生成されたものをテストすることにはあまり意味はありません。

<img width="512" alt="スクリーンショット 2018-05-26 15.07.45.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/1958531d-9e87-6fc8-9405-39b2d3516463.png">

このような理由で、Lombokで生成されたコードをテストから除外する指定をしてみます。
プロジェクトのディレクトリに下記のファイルを置き、もう一度テストを実施してみます。

```lombok.config
lombok.addLombokGeneratedAnnotation = true 
```

```sh
$ ./gradlew clean
$ ./gradlew test
$ ./gradlew jacocoTestReport 
```

で、カバレッジがこのように変わります。テストコードをまだ書いていないので赤い状況は変わりまえんが、domainのところが大きく減り、全体のコードのInstructionが、731から170に大きく減っています。

<img width="512" alt="スクリーンショット 2018-05-26 15.17.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/d028f6c1-da35-b3df-fcc4-a611f249728f.png">

lombok.configに指定可能な値の説明は、[lombokの公式サイト](https://projectlombok.org/features/configuration)に説明があります。

ここまでのものを[GitHubにv0.2として](https://github.com/hrkt/lombok-jacoco-sample/releases/tag/v0.2)置きました。

## テストを追加

上記のものに、単体テストで、とりあえず1パスは通るようにしてカバレッジを100%にした状態が、[GitHub上のサンプルv0.3](https://github.com/hrkt/lombok-jacoco-sample/releases/tag/v0.3)になります。

<img width="512" alt="スクリーンショット 2018-05-26 17.09.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/0679a413-cbb9-063a-3c4e-a995ae1b5bb8.png">

(参考)これで、除外設定をもとにもどす（指定しない）と、このような状況になります。

<img width="512" alt="スクリーンショット 2018-05-26 17.13.05.png" src="https://qiita-image-store.s3.amazonaws.com/0/65002/b06d2615-b21d-d6f8-dd79-3722804bffd7.png">

## まとめ

このエントリでは、lombokで自動生成されたコードをカバレッジの計測から除外する方法を確認し、実際に動作するところまでをサンプルコードを通して示しました。

lombokの機能は本エントリのサンプルで使った以外にも[本家のサイトでいろいろ](https://projectlombok.org/features/all)紹介されているので、別の機会に利用例を紹介したいと思います。