GeekFactory

int128.hatenablog.com

KPTとYWTMの振り返り手法を使い分ける

チーム開発の振り返り(レトロスペクティブ)ではKPTが広く使われていますが、YWTMという手法もあります。

yu-hi.babymilk.jp

私のチームでは、スプリントの振り返りはKPT、一定の区切り(月とか四半期)での振り返りはYWTMを使っています。具体的には、個人ワーク(5分で付箋を書く)とグループワーク(順番に付箋を共有して議論する)を以下のキーワードごとに繰り返す進め方でやっています。

キーワード 議論すること
K スプリントでよかったこと、今後も続けたいこと
P スプリントで問題と思ったこと
T 次のスプリントで改善したいこと
-- --
Y 今月(今期)やったこと
W 今月(今期)やって分かったこと
T 来月(来期)やること
M 来月(来期)やって改善されること

スプリントのような短期間であればチームの活動がみんなの記憶に残っていますが、月や四半期になるとチームの活動を思い出す必要が出てきます。さらに、チームではいろんな活動をしているので、個人によって見え方(フレーム)が違うものです。やったことを共有して共通認識を形成できるのがYWTMの強みと言えるでしょう。

ただし、YWTMはチームでやったことを挙げていくので話題が発散しやすく時間がかかります。スプリントのような短期間であれば、話題を発散させるよりも問題の改善に集中した方が効率的に議論できます。以前、別のチームでスプリントごとにYWTMをやってみたのですが、冗長な感じがしてすぐにやめてしまいました。このような理由でスプリントのふりかえりにはKPTを採用しています。

KPTやYWTMで出てきた「T」は次のスプリントのバックログに入れるようにしています。これにより、振り返りと改善のサイクルが回るように工夫しています。もし、アーキテクチャの変更のような大きな話が出てきた場合は次のスプリントだけでは収まらないので来期の計画に盛り込みます。走りながら改善を続けるのは難しいですね。

Gradle TestKitでプラグインをテストする

GradleプラグインのテストにGradle TestKitを利用すると便利です。

何に使うの?

ユニットテストではうまく動いているのに、実際のプロジェクトにプラグインを適用するとうまく動かないことがあります。これはGroovyのバージョンが違うとか、ビルドスクリプトのスコープで暗黙的に定義されている変数があるといったことが原因です。MetaClassやdelegateで特殊なことをしていると起こることがあります。

実際のプロジェクトにプラグインを適用するには、以下の手順が必要になります。

  1. プラグインのJARをビルドする。
  2. プラグインをローカルのMavenリポジトリに配置する。
  3. テスト対象プロジェクトでローカルのMavenリポジトリにあるプラグインを読み込む。
  4. テスト対象プロジェクトでタスクを実行する。

Gradle TestKitを利用すると、以下の手順で済みます。

  1. テスト対象プロジェクトのタスクを実行するテストコードを実行する。

簡単ですね。ただし、Gradle TestKitによるプラグインのテストはGradle 2.13以降で使えます。2.13より前の場合は自分でクラスパスを設定する必要があります。詳しくは下記を参照してください。

https://docs.gradle.org/current/userguide/test_kit.html#sub:test-kit-automatic-classpath-injection

ここでは、実際のプロジェクトにプラグインを適用して期待通りの動作を確認することを受け入れテストと定義します。受け入れテストを実行するには以下が必要です。

  • 受け入れテストのプロジェクト
  • 受け入れテストのテストコード
  • テスト対象プロジェクト

まず、受け入れテストのプロジェクトを見ていきましょう。TestKitを有効にするため java-gradle-plugin を適用します。また、テスト対象のプラグインが別のプロジェクトにある場合は gradlePlugin { pluginSourceSet } で指定します。

// acceptance-test/build.gradle : 受け入れテストのプロジェクト
plugins {
    id 'groovy'
    id 'java-gradle-plugin'
}

repositories {
    jcenter()
}

dependencies {
    testCompile('org.spockframework:spock-core:1.0-groovy-2.4') {
        exclude module: 'groovy-all'
    }
}

gradlePlugin {
    // 親プロジェクトにテスト対象のプラグインがあることを指定します
    pluginSourceSet parent.sourceSets.main
}

受け入れテストのテストコードは以下になります。 withPluginClasspath() を指定すると、プラグインのクラスパスが自動的に追加されるようになります。

// acceptance-test/src/test/groovy/AcceptanceSpec.groovy : 受け入れテストのテストコード
import org.gradle.testkit.runner.GradleRunner
import spock.lang.Specification
import spock.lang.Unroll

class AcceptanceSpec extends Specification {
    @Unroll
    def 'acceptance test should pass'() {
        given:
        def runner = GradleRunner.create()
            .withProjectDir(new File('fixture'))
            .withArguments('test')
            .withPluginClasspath()

        when:
        runner.build()

        then:
        noExceptionThrown()
    }
}

テスト対象プロジェクトは以下になります。ここでは単純にプラグインが存在するかassertしています。

// acceptance-test/fixture/build.gradle : テスト対象プロジェクト
plugins {
    id 'com.example.hello'
}

task test << {
    assert project.plugins.hasPlugin('com.example.hello')
}

応用編

withGradleVersion() を使うと、複数のバージョンでプラグインが動作するかテストできます。SpockのData Driven Testingを使えば簡単に書けます。

        given:
        def runner = GradleRunner.create()
            .withProjectDir(new File('fixture'))
            .withArguments('test')
            .withPluginClasspath()
            .withGradleVersion(gradleVersion)

        where:
        gradleVersion << ['3.1', '2.13']

Gradle Plugin Starterで実際に使っているので参考にしてください。

github.com

まとめ

Gradleのプラグインを実際のプロジェクトに適用して期待通りの動作を確認するにはこれまで面倒な手順が必要でしたが、Gradle TestKitを利用すれば簡単に実現できるようになりました。

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

Gradle徹底入門 次世代ビルドツールによる自動化基盤の構築

REST APIの例外設計

REST APIを設計する場合に、エラーをどのステータスコードで返却するか議論になることがあります。例えば、以下のような場合が挙げられます。

  • キー指定のリクエストでDBにデータがない場合(例: GET /books/1
  • 一覧のリクエストでDBにデータがない場合(例: GET /books
  • 必須項目がない、型が合わないといった場合(例: GET /books/find?count=bar
  • ビジネスルールに違反する場合(例: POST /purchase
  • 実行時エラー(例: NullPointerException

クライアントが適切にエラーを処理できるように、レスポンスにエラー原因を入れることが一般的です。では、ステータスコードは何がいいのでしょうか。HTTPのステータスコードRFCで定義されているし、RESTの考え方はWebや書籍にまとまっていますが、あくまでも考え方なので人によって意見が分かれる場合があります。

2つの論点を挙げて考えてみましょう。

論点1. レイヤの責務と障害の切り分け

Spring Bootのアプリケーションを例に考えてみましょう。クライアントから見ると以下のレイヤがあります。

キー指定のリクエストでDBにデータがない場合、404を返す設計が一般的です。しかし、アプリケーションサーバフレームワークなどのインフラ層も404を返す場合があるため、区別できるようにアプリケーション層は一律で200を返した方がよいという意見もあるでしょう。何か障害が発生した場合に、インフラ層の設定が悪いのか、リクエストのURLが間違っているのか、DBにデータがないだけで想定通りなのか、ステータスコードだけでは切り分けができません。

入力チェックで400を返す、実行時エラーで500を返すといった場合も同じことが言えますね。

これに対しては、エラー原因をレスポンスヘッダやレスポンス本体に含めることで、切り分けが可能になります。APIクライアントはレスポンス本体から原因を特定して、適切なUXを実装します。システム監視では、ステータスコードとレスポンスヘッダから原因を特定して、適切な監視条件を設定します。レスポンスが200である必要はないでしょう。

論点2. 宣言的なAPIクライアント

エラーの場合に200を返す設計とした場合、APIクライアントはレスポンスの内容から実行時に型を判断する必要が出てきます。例えば、 GET /books/1 のレスポンスを Book クラスに入れるのか Error クラスに入れるのか、実行時に判断する必要が生じます。

Feignなどの宣言的なAPIクライアントは、URLに対してレスポンスの型が一意に決まるように設計されています。実行時に型が決まるような設計とは相性が悪いといえます。詳しくは調べていないのですが、200でエラーレスポンスを処理するには特別な実装が必要になると思われます。

結論

リソースの内容を返す場合は200、エラーの場合は404や500などを返す設計でよさそうです。