劇的改善 CI 4時間から5分へ
〜私がやった10のこと〜
リクルートライフスタイル
R-SET 関根康史
JJUG CCC Fall 2017
自己紹介
● 関根 康史 ( @AHA_oretama )
● リクルートライフスタイル
○ 2015/8 〜
R-SET活動中
The SET or Software Engineer in Test is also a developer role except their focus is on testability.
R-SET is Recruit Lifestyle’ SET.
ミッション
プロダクト・サービスの品質向上⤴
エンジニアの開発生産性向上⤴
いままで起きていた負
1回に3〜4時間かかっていたCI…
深夜に一度しか回すことができなかった
CI=継続的インテグレーションと呼べない状態になりつつあった…
CIは速いほうがよい
⇒ 不具合は早く見つける方が対策費用を抑えられる
※一般にCIは10分以内に終わるのが良い、と言われている。
遅くとも20分以内に終わるのが望ましい。
逆に遅くなったときに起こりうることは?
● 別の人のコミットが混じりやすくなり、テストの失敗原因の特定に時間がかかる
● 作成者が作ったものを忘れる
● 作成者が自分の分のコミットでCIが回っていることを忘れる
● エンジニアがテスト結果に興味がなくなる
● テストが失敗したまま放置されやすくなる
● テスト自体回さなくなってしまう
● 緊急リリースを行う前にCIを回さず、デグレが本番で発生する
● リリース日にエラーしているとその日のリリースをとりやめなければならなくなる
なぜCIを早くしたほうがよいか
私がやった10のこと
その前に…
対象のアプリケーションの説明
● Spring Boot / Web APIアプリケーション
● Controller -> Service -> Repository
というシンプルなアーキテクチャ
Controller 主にValidation
Service ビジネスロジック
Repository DBアクセス、APIアクセスなど
Controller
Service
Repository
RDB
MyBatis
● 主にRDBへ接続してデータを取得(MyBatis使用)
● CIで実行していたのは主にビルド -> 単体テスト
Controller
Service
Repository
RDB
1. テストクラスの責務の明確化
層をまたいだテスト、
クラスを結合したテストは禁止
⇒ クラス単位のテストへ
ただし、RDBへのアクセス(SQLを含め)は
業務上、十分にテストすべきであった
⇒Repository -> RDB はまとめてテスト
MyBatis
2. Controllerテストのannotation変更
● Not @SpringBootTest, but @WebMvcTest
@SpringBootTest @WebMvcTest
概要 Spring applicationを起動して
テストする
Spring MVC infrastructureをAuto-configuredして
テストする
Bean 全てのBean @Controller, @ControllerAdvice,
@JsonComponent, Filter, WebMvcConfigurer,
HandlerMethodArgumentResolver
テスト
時間
Webサーバまで起動するため
非常に遅い
Spring MVCに必要なBeanしか登録されないため
早い
類似 - @JsonTest, @DataJpaTest, @JdbcTest … etc.
3. ServiceテストでDIを使わない
Service層はアプリケーション外とのやりとりがない
⇒ Springを使わない、シンプルなJUnitテスト(RepositoryはMock化)
※ 以下の例はSpock
class CalendarServiceTest extends Specification {
@InjectMocks
private CalendarService service
@Mock
private CalendarRepository calendarRepository
def setup(){
MockitoAnnotations .initMocks(this)
}
// Tests are here.
}
4. RepositoryはFilterによるBean選択
mybatis-spring-boot-starter-test 1.3(2017/04/10)から
@MyBatisTest が登場し、必要なBeanのみを登録できるようになった。
⇒ 1.3以上を使っている方は@MyBatisを使えばよい。
自身のプロジェクトでは、まだ上記がなかったため、
登録するBeanをフィルタリングするAnnotationを作成しようとした。
⇒ (Mybatisに限らず)必要ならBeanをフィルタリングすることが必要!
5. 負債となったテストの改善
テストはある程度の規模の開発では爆発的に増える。
その結果、負債となるテストも出てくる。
⇒ 一つ一つ潰す
ex.
● 本番でしか繋がらないサーバに対して、通信を行い、通信エラーになるまで数十秒間待機する。
● 10000件以上のデータ登録でエラーになるテストケースで
10000件のデータを登録しようとする。
● Debugログレベルが大量に出力されてしまい、ログ出力に時間がかかっている。
6. テストの削除
ときにはテスト自体を削除する決断も必要
● 不安定なテスト
● 保守しにくいテスト
● テスト責務が広すぎるテスト
● 過度なテスト
7&8. CIの再構築&並列テスト環境の構築
Oracle
社内サーバ Node
Node
● 性能が良くない社内サーバから AWSへ(性能&安定性UP)
● ジョブ数に応じて、AMIからNodeを自動生成するようにして自動でスケール
● RDS内部でスキーマを切り、 Nodeごとに別の仮想DBを用意
● テストの並列実行が可能に
Schema
Schema
9. テストサイズの導入
Googleが提唱するテストサイズ導入と、テストサイズによる実行タイミング分割
Smallテスト Mediumテスト Largeテスト
時間的目標
(メソッドごと)
100ms未満で実行 1s 未満で実行 できる限り高速に実行
ネットワーク モック localhostのみ ◯
データベース モック ◯ ◯
他システムへの
アクセス
モック 推奨されない ◯
実行タイミング プルリクエストごとに ベースブランチにマージ
されたタイミング
任意、またはリリースごとに
9. テストサイズの導入
JUnitの@Category,maven-surefire-pluginのgroupsで実現する。
<project ...>
<properties>
<testcase.groups></testcase.groups>
</properties>
<profiles>
<profile Small ...>
<profile Medium ...>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<groups>${testcase.groups}</groups>
</configuration>
</plugin>
</plugins>
</build>
</project>
mvnのprofileで実行する
テストサイズの切り替え
$ mvn test -P Small
$ mvn test -P Medium
$ mvn test
参考
:https://qiita.com/AHA_oretama/items/6239
aac9eafd397ebf4e
10. プルリクエストFeedBack
● 4時間かかっていたCIがSmallテストサイズで5分、
Mediumテストサイズで20分程度で終わるようになった。
 ⇒プルリクエスト単位でCIの結果をフィードバックを行う!
10. プルリクエストFeedBack(番外編)
プルリク上で指摘・可視化できるすべて行う
● Lintの指摘(lint-review)
● カバレッジの可視化(CodeCov)
● 負債の指摘(SonarQube)
● ライブラリのアップデート(VersionEye)
● チーム独自のルール(Danger)
※VersionEyeが今年でサービス終了するため継続不可
10. プルリクエストFeedBack(番外編)
まとめ
● CIが3〜4時間かかり、継続的インテグレーションと呼べない状態になりかかってい
た
● CIの速度改善として、10のことを行った
● 結果として開発者へ早期にフィードバックができるようになり
開発生産性、品質の向上を行った

劇的改善 Ci4時間から5分へ〜私がやった10のこと〜