Gradle+Spring BootによるREST-APIの開発支援環境を体感してみた 【プログラミング工程編】


TL;DR

タイトルはよくある話になってしまっていますが、業務として開発するにあたってソースが外に出せない、数多の便利なPaaS/SaaSを使うことがなかなか許可されない、といった状況にある方もいらっしゃると思います。
ならば手元に作るしかないじゃない!ということで以下のような開発・テストを支援する環境を構築してみた話を書きます。

  1. プログラミング、ユニットテスト、静的解析、カバレッジレポート集計(この記事)
  2. 開発環境から実動環境への載せ換え(Spring Bootで作成したAPIをwarにし、tomcat環境で実行)、機能テスト
  3. 性能検証

1、2、3とだんだんと工程が進んでいくような想定で記事は分割して書いていこうと思います。

なお、ここで書く話は実際の業務運用としてそのまま適用できるという深いレベルではなく、Dockerなどを利用してサッと環境を構築して現場レベルで始めてみよう的に簡略化した内容となっています。
話の流れとしては私が個々のツールなどについて調べた内容を業務シナリオに沿った形でまとめたつもりなので、社内勉強会やハンズオン的にも使える内容にしたいと思っています。

前提

以下のあたりの技術要素については、ある程度知識・経験があるものとしますので、詳しい話は省いてしまうかもしれません。他のWeb記事なども参考にしてください。

  • Gradle+Spring-Bootによる開発・テスト
  • Docker
  • Rest-API

次に挙げる内容については、記事の内容とあまり依存せず適用できる話だと思いますので触れません。

  • ソースコード管理の話
  • Jenkinsなどを使って自動化する話

私は普段Windows PCで作業していますので、以降のコマンドライン記述はWindowsベースになります。環境に合わせて読み替えてください。

開発環境のツール・バージョンなど

本記事では開発環境(ローカルPC)+インターネット接続で動作するようにしています。そのため、以下ツール類がインストール済み&動作する環境をご用意ください。

  • Java+IDE: Pleiades All in One Eclipse(Oxygen)で用意。JDKはパッケージに含まれているJava8を使用。
  • docker: Docker for Windows Version 17.12.0-ce-win47 (15139)
  • docker-compose: version 1.18.0, build 8dd22a96

使用するサンプル

私が最近の業務で使った環境になってしまいますがTomcat+PostgreSQLで運用するAPI層を作ることにします。サンプルとしては以下の記事のものを使わせていただきました。

Spring Boot, PostgreSQL, JPA, Hibernate RESTful CRUD API Example

ただ、ここではビルドツールとしてGradleを使用しますので、記事からリンクされているGitHubリポジトリをForkしてGradle化したものをこちらのGitHubに用意しました。とりあえず動かしてみたいという方はcloneしてきてください。
コマンドプロンプトから、プロジェクトフォルダに移動し、以下のコマンドでビルドが完了するかを確認します。(ユニットテストをスキップしないとDB周りでエラーになるのでtestタスクはスキップさせます。)

.\gradlew build -x test

GradleのProxy設定

gradlew実行時にファイルを取得しに行きますが、Proxy環境下で引っかかってしまう場合は、[ユーザーホーム]\.gradle\gradle.propertiesに以下のProxy設定を追記します。

gradle.properties
systemProp.http.proxyPort=8080
systemProp.http.proxyHost=myproxy.example.com
systemProp.http.proxyUser=********
systemProp.http.proxyPassword=********
systemProp.https.proxyPort=8080
systemProp.https.proxyHost=myproxy.example.com
systemProp.https.proxyUser=********
systemProp.https.proxyPassword=********

Spring Bootのプロファイルをローカルテスト用に設定

方法はいくつかありますが、ここでは環境変数に以下の値を設定してapplication-develop.propertiesを読み込ませるようにします。切り替えている設定はDBの接続情報だけです。

  • 変数名: SPRING_PROFILES_ACTIVE
  • 値: develop

ローカルテスト用にPostgreSQL環境の用意

このサンプルを動かすためにはPostgreSQLが必要になりますが、後々の記事でも使えるようDocker & docker-composeでtomcat+postgresqlの環境を立ち上げ、そのDBに直接つなげるようにします。

こちらについてもGitHubリポジトリに登録しましたのでクローンしてご利用ください。(後で利用する他のdockerファイルも含んでいます。)

ダウンロードしたリポジトリ内のtomcat-postgresフォルダに移動し、docker-compose up -dでtomcat+postgresqlを起動します。正常に立ち上がると以下のポートでそれぞれ起動されているはずです。

  • Tomcat8: localhost:8081
  • PostgreSQL: localhost:5432

正しく接続できていれば.\gradlew buildコマンドがエラーにならずに完了するかと思います。

コーディング

やったことにしてまるっと省略。

静的解析・カバレッジレポートの集約

Gradleのjacocoプラグインを使用することで簡単に取得・レポート作成ができますが、ここでは静的解析ツールのSonarQubeを使って開発者のPCから解析結果を集約してプロジェクトとして見れるようにしていきます。

jacocoプラグインの設定

SonarQubeはSonarJavaプラグインで静的解析はやってくれますが、ユニットテストとカバレッジレポートについてはローカルPC側で取得した結果が必要になります。標準でjunit+jacocoの結果はサポートしてくれていますので、build.gradleにjacocoプラグインの設定があるかを確認しておきます。

build.gradle(抜粋)
buildscript {
    ext {
        jacocoVersion     = '0.7.9'
    }

apply plugin: 'jacoco'

jacoco {
    toolVersion = jacocoVersion
}

デフォルトでは[Gradleプロジェクトルートフォルダ]\build\jacoco\text.execが解析されますので、必要に応じて設定を合わせてください。

SonarQubeの起動

SonarQube構築用のdocker-composeファイル一式もGitHubリポジトリに含めていますので、以下のように起動してください。

> cd [クローンしたフォルダ]\sonarqube
> docker-compose up -d

アクセスURLは http://localhost:9000/ になります。管理画面へはadmin/adminでログインできると思います。

注)起動後すぐにアクセスしてもエラー画面になりますので、しばらくお待ちください。(docker-compose logs -fなどで状況を確認しつつ)

SonarQubeのProxy設定

Proxy環境下では管理画面からマーケットプレイスにあるプラグイン一覧が表示されない場合があります。その場合はdocker-compose.ymlファイルに以下のようにProxy周りの変数を設定してイメージを作成してください。

docker-compose.yml
version: '2'
services:
  sonarqube:
    build:
      context: ./sonarqube
      args:
        PROXY_HOST: myproxy.example.com
        PROXY_PORT: 8080
        PROXY_USER: ********
        PROXY_PASSWORD: ********
    container_name: sonarqube
    restart: always
    ports:
      - 9000:9000
    environment:
      - SONARQUBE_JDBC_URL=jdbc:postgresql://sonarqube_db:5432/sonar
(略)

Gradleプロジェクト側のSonarQubeプラグイン設定

build.gradleの設定としては、サンプルプロジェクトにはすでに記述済みの以下設定が該当します。

build.gradle(抜粋)
buildscript {
    ext {
        sonarqubeVersion  = '2.6'
    }
    dependencies {
        // SonarQubeプラグイン
        classpath "org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:${sonarqubeVersion}"  }
}

apply plugin: 'org.sonarqube'

sonarqube {
    properties {
        property "sonar.jacoco.reportPath", "${project.buildDir}/jacoco/test.exec"
    }
}

GradleへのSonarQube送信先設定

GradleのProxy設定と同様にシステムプロパティに以下のように設定します。

systemProp.sonar.host.url=http://localhost:9000/

解析の実行

SonarQubeが動作している状態でサンプルプロジェクトのルートフォルダから以下のコマンドを実行します。

> cd [gradleプロジェクトのルートフォルダ]
> .\gradlew sonarqube

SonarQubeへの接続がうまく行かなかった場合などはsonarqubeタスクが失敗するはずですので、原因を取り除いてBUILD SUCCESSFULとなるように対処してください。

解析結果の確認

うまく結果が送信されるとこのように静的解析やカバレッジのデータが表示されます。

プロジェクト名のリンクをクリックすると時系列の推移(後述)を含めた若干詳しい情報が表示されます。

ユニットテストを書いてみる

サンプルにはテストコードが書かれていないので、簡単なテストコードを追加してみます。(ちゃんとしたテストっぽくない点はご容赦ください。)

com.example.postgresdemo.controller.QuestionControllerTest.java
package com.example.postgresdemo.controller;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;

import com.example.postgresdemo.repository.QuestionRepository;

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class QuestionControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private QuestionRepository questionRepository;

    @Test
    public void testPostQuestion() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.post("/questions")
            .content(
                "{\"title\":\"How to use PostgreSQL with Spring Boot and JPA\",\"description\":\"I want to use PostgreSQL with Spring Boot and JPA and develop RESTful APIs. Please help!\"}")
            .contentType(MediaType.APPLICATION_JSON)
            .accept(MediaType.APPLICATION_JSON))
            .andExpect(MockMvcResultMatchers.status().is2xxSuccessful());
    }

    @Test
    public void testGetQuestion() throws Exception {
        mockMvc.perform(MockMvcRequestBuilders.get("/questions"))
            .andExpect(MockMvcResultMatchers.status().is2xxSuccessful());
    }

}

ユニットテストが正常に動作することが確認できたら、再度.\gradlew sonarqubeコマンドでSonarQubeへ結果を送信します。
そうすると詳細画面では前回結果との差分やカバレッジ率の時間的推移などを確認することができます。

わざとテストコードを書かずにAPIを追加し、同様に解析をしてみると、以下のように差分に対するカバレッジが0%ですといった解析結果となります。

Quality Gateという欄に「Failed」と表示されていますが、SonarQubeのデフォルトとして以下のような観点としきい値で判断されるようになっています。

テストコードを追加していくとさらに次のように変化していくので、時系列で見えてくる課題(例えば後半ユニットテストの無駄が多い、など)にも対策を検討することができるかと思います。

まとめ

このような環境がプロジェクトとして共有できていると、進捗報告の場では原因と対策だけで議論が集中できたり、そもそもその場を待たずしてよりタイムリーな対策が打てるなどのメリットも出てくるかと思いました。

冒頭で「書きません」と宣言したソースコード管理や自動化を絡めた話は、以下のApplibotさんのブログに詳しく書かれていますので、参考にして是非チャレンジ&実運用してみてください!
SonarQubeで始める静的コード解析

Next Step

次回は作ったAPIが外部と連携していく工程を想定し、以下あたりについて書けるとよいかなと考えております。

  • Springfoxを導入してSwaggerドキュメントを自動生成
  • 実動環境にwar形式でデプロイ(環境情報の切り替えなど)
  • Swaggerドキュメントの活用法(機能テスト、etc.)

参考にさせていただいたサイト