ヤフー株式会社
MSC開発本部
マーケッターPF開発部
森下⼤大介
広告システム刷新
よもやま話
テストが当たり前になる
までにやったこと
⾃自⼰己紹介
YJ⼊入社前
Sierな世界の住⼈人。
受託案件をこなす業務システム開発
のエンジニア。
YJ⼊入社後
BtoBなWebサービス開発へ。
Web系と⾔言いつつエンタープライズ
系っぽいシステムの開発に携わる。
• 名前:森下⼤大介
• 年年齢:40
• 職歴:2011年年にYJに中途⼊入社
• ⾔言語:C/C++/Java
今回のお話の前提
⾃自分の所属部署における
取り組み事例例。
BtoBでエンタープライズシステム的。
ヤフーというWebサービス会社の中
でも、現場はかなりSIerよりな雰囲
気があるところ。
そんな開発部署で、利利⽤用技術
や道具を⾃自分たちで決めたら
こうできた的な感じのお話。
4P本⽇日の内容
• バックグラウンド
• 取り組んだこと
• やったことその0:体制づくり
• やったことその1:プログラミング⾔言語を変える
• やったことその2:テストができるアーキテクチャに
• やったことその3:テスト向けDSL (Spock)
• やったことその4:CI/CD,   静的解析
• やったことその5:インタフェース定義⾔言語
• 振り返っての所感
バックグラウンド
当時困っていたこと
「なんか妙につらい」
例例1:⼩小改修なのにつらい
typo修正したいだけなのに・・・
-‐‑‒ ユニットテストがない
-‐‑‒ コンパイラ、型がない
-‐‑‒ IDE使ってない
例例2:結合がつらい
なかなかつながらない・・・
-‐‑‒ APIIFドキュメントが⼿手書き、書き⽅方が統⼀一され
てると⾔言えない
-‐‑‒ APIIFドキュメントの書き⼿手と読み⼿手でお互いに
認識識がズレてる
-‐‑‒ APIIFドキュメント通りに実装されていない。
例例3:なんとなく動いてつらい
LLの柔軟さが裏裏⽬目に・・・
-‐‑‒ 存在しないメソッドコールしても動く。
-‐‑‒ 間違った型で引数受け取っても動く。
何が起きてる?
考察1:「⼤大規模」で「複雑」
サービスが育った結果・・・
-‐‑‒ 道具の助け無しで
-‐‑‒ ⼈人⼿手と記憶と努⼒力力と根性で
-‐‑‒ がんばれる規模をこえている
考察2:道具とやり⽅方が合ってない
ちゃんとデータを扱いたいのに
-‐‑‒ 型宣⾔言が無い
-‐‑‒ コンパイラに頼れない
-‐‑‒ テストを仕掛けられない
考察3:積み重なる技術負債
やり⽅方そのままで
-‐‑‒ 既存コンポーネントは拡張され
-‐‑‒ 新規コンポーネントが追加され
-‐‑‒ そしてそこにテストは無い
そんなとき
組織変更更後の
部⾨門⻑⾧長より
イチから全部変えろ!
という無茶茶ブリ
機会をいただいた。
こういうキッカケは⼤大事。
⽬目指したこと
変わるべきは「⾃自分達」
• 3倍早く
• やりやすく
• 確実に
開発できる強いエンジニアになること。
システム刷新は「結果」
• ⾃自分の変化がアウトプットを変える。
• でも結果を出すことも重要。
• 結果で証明する。
3倍早い開発スピード
取り組んだこと
やったことその0
体制づくり
その1:リード役の配置
⾃自分が当時の役職を離離れて、部⻑⾧長
直下のぼっち「部付」となり、刷
新活動に集中。
その2:仲間づくり
同じような問題意識識を持つ⼈人に声
をかけて、バーチャルなチームを
構築。
その3:部⾨門⻑⾧長による宣⾔言
刷新していくことを部の内外でス
テークホルダー/部内メンバーに
宣⾔言。
やったことその1
プログラミング⾔言語
を変える。
これを決めた時点で学習コス
トが極⼤大化するのは確定。
⼤大抵はそうしないで済む⽅方向
を考えますが・・・
とにかく欲しかったのは
• コンパイル!
• 型宣⾔言!
「⼤大規模で、データをきっち
り扱うシステム」
をちゃんと開発するために必
要な変化だと判断。
求めた効果は、
しょうもないレベルの間違い
を動かすまでもなく潰せるこ
と。
これができないと、typo修正
すらおそろしくなる。
• 動かして全確認が必要
• 漏漏れが無いか確証が持てない。
考えたのは以下あたり。
• C++
• Java
• Scala
この条件でいくと、現実的な
選択肢はだいぶ絞られる。
選んだのは
Java
Webサービス系開発者に
はとかく嫌われる、ディ
スられるが・・・
副次的なものとして
• 豊富なOSS
• 統合開発環境の活⽤用
• 優れた静的解析ツール
これで得られたもの
• コンパイルできる!
• 厳密な型定義できる!
• 優れたメモリ管理理(GC)
• 実⾏行行時最適化(JIT)
• デバッガ、解析ツール
やったことその2
テストできる
アーキテクチャに
テストできないとは?
• 動かす準備が⼤大変
• ブラックボックステストになる
• CI/CDの中で実⾏行行できない
• etc…
根本的な原因として、プロダクトが
「モノリシック」なカタマリになっ
ているためと仮定。
ユニットテストしたい単位で単独で
動かせない状態。
ただ、現⾏行行システムで既にサーバー
分割とAPI化によって準マイクロ
サービス的な構成となっていた。
おかげで考慮対象はサーバー上で動
くアプリケーションコードのみで済
んだ。
16Pモノリシックなコードの例例
これらは⼀一⾒見見するとA,  B,  Cというクラスに分
割されているように⾒見見えるが・・・
public	
  class	
  A	
  {
public	
  void	
  x()	
  {
B	
  b	
  =	
  new	
  B();
b.y();
}
}
public	
  class	
  B	
  {
public	
  void	
  y()	
  {
C	
  c	
  =	
  new	
  C();
c.z();
}
}
public	
  class	
  C	
  {
public	
  void	
  z()	
  {
DB操作とか
}
}
17Pこれにテストケースを仕掛けてみると・・・
public	
  class	
  A	
  {
public	
  void	
  x()	
  {
B	
  b	
  =	
  new	
  B();
b.y();
}
}
public	
  class	
  B	
  {
public	
  void	
  y()	
  {
C	
  c	
  =	
  new	
  C();
c.z();
}
}
public	
  class	
  C	
  {
public	
  void	
  z()	
  {
DB操作とか
}
}
TestCaseA
(A,	
  B,	
  Cの複合テスト)
Class  AにテストケースつくるとB,  Cも必ず
くっついて来て単独テストにならない。
TestCaseB
(BとCの複合テスト) TestCaseC
(これはまだ単独)
ポイントは、
「呼び出し先の実装クラスを⾃自分で
newしていること」
これだとクラス同⼠士が密結合する。
このようなことから、
「テストできるような構造じゃない
からやらない」
という結論論となり、放置される。
この状態で無理理にテストケースを仕
掛けたとしても、
• 実⾏行行条件が増える
• バリエーションが掛け算で増加
• メンテ、問題箇所の特定が困難
これを解決するために全⾯面的に採⽤用
したアーキテクチャが
「Dependency  Injection」
19PDI(JavaでSpringFramworkの場合)
public	
  class	
  Aimpl
implements	
  A	
  {
@Autowired
private	
  B	
  b;
public	
  void	
  x()	
  {
b.y();
}
}
public	
  class	
  Bimpl
implments B	
  {
@Autowired
private	
  C	
  c;
public	
  void	
  y()	
  {
c.z();
}
}
public	
  class	
  Cimpl
implements	
  C	
  {
public	
  void	
  z()	
  {
DB操作とか
}
}
実装クラスとインタフェースを分離離、コール先のインタフェースのみ認識識
し、実装インスタンス(Dependency)は外部から注⼊入(Injection)。
20Pこれにテストケースを仕掛けてみると・・・
public	
  class	
  Aimpl
implements	
  A	
  {
@Autowired
private	
  B	
  b;
public	
  void	
  x()	
  {
b.y();
}
}
今まではA単独のテストができなかったが、依存するBをモック化するこ
とでテストしたい処理理だけに対して確認を⾏行行えるようになる。
class	
  TestCaseA {
def testA()	
  {
def a	
  =	
  new	
  Aimpl()
//モック注入
a.b =	
  Mock(B.class)
//テスト実行、assert
a.x()
}
}
これにより、依存する他モジュール
をnewしなくなる。
そうなると、テスト時にモックを⾃自
由に差し込めるようになるので単独
テストが可能となる。
ただし、絶対にあらゆるクラスの
newが禁⽌止というわけではなく、
「テストの都合で分離離しててほしい
単位」
でこれが適⽤用されていればよい。
利利⽤用してるDIコンテナはOSSの
「SpringFramework」
やったことその3
テスト向けDSL採⽤用
(Spock)
当初使おうとしていたのは定番のこ
のあたり。
• JUnit
• JMock
ただ・・・
テストケースの作成に労⼒力力が掛かり
過ぎるようだと、やはり
「テスト書くのがきつすぎるのでや
らない」
ということになる。
JUnitはJava⾔言語でテストを書くこ
とになるが、Java⾔言語はテストを
表現する⽂文法を持たない。
またJMockはかなり変態コードにな
るため更更にきつい。
そこで選んだのが
Spock
Junitの上に構築されたものだが、
記述⾔言語は、JVM⾔言語の
「Groovy」
その上にテストを記述するための
DSL(ドメイン固有⾔言語)が構築され
ている。
主な機能としては
• テスト実⾏行行
• BDD的なテスト記述⽂文法
• 柔軟なモック/スタブ⽣生成
• テストパターンデータの記述
特に良良いのが、Groovy⾃自体がJava
よりも⾊色々省省略略して書けること。
テストコードはJava⾔言語では書き
たくない。
24PSpockによるテストコード例例
class	
  SampleSTest extends	
  Specification	
  {
def “データ更新テスト(#testname)”()	
   {
given:
def target	
  =	
  new	
  SampleImpl()
target.x =	
  Mock(X.class)
when:
def result	
  =	
  x.update(request)
then:
assert	
  result	
  ==	
  response
where:
testname |	
  request	
  |	
  response
“パターンA”	
  |	
  “foo1”	
  |	
  “bar1”
“パターンB”	
  |	
  “foo2”	
  |	
  “bar2”
}
}
テストケースで以下のようなブロックに区切って
コードを書ける。
• givenがテスト対象のセットアップ
• whenがテスト対象の実行
• thenがテスト結果の確認
• whereがテストデータ
Mockの生成とその挙動も全てテストコードの中
で記述できるのが良いところ。
テストデータを複数件かけばその件数分で全
体をループして実行してくれる。
Groovyの型推論や省略記法も楽で助かる。
やったことその4
CI/CD,  静的解析
アーキテクチャとテストケースそれ
ぞれのアプローチからテストができ
ない理理由を取り除いた。
これでやっとCI/CDの中であたりま
えにテストを⾏行行うようになった。
さらに⾏行行っていることその1
Cloverによるカバレッジ計測
さらに⾏行行っていることその2
Coverityによる静的解析
• Quality  Advisor
• Test  Advisor
やったことその5
インタフェース
定義⾔言語
こちらはテストとは別の観点からの
開発効率率率アップと品質向上にむけた
取り組み。
システム全体をマイクロサービス
アーキテクチャで構成すると、バッ
クエンドの各機能を
「Webサービス(API)」
として提供する。
その実装とテストのためには以下を
⾏行行うことになる。
• 外部仕様(APIIF)の定義と公開
• 外部仕様どおりの実装
• 結合試験
でも・・・
IFって静的なものなんだから、宣
⾔言的に記述できるはず。
宣⾔言的な記述ならそこからコード
も⽂文書も⽣生成できる。
API提供側では、
⼈人がAPIIFを考えて、それを頑
張ってドキュメントとして書く。
それを読んで実装する。
API利利⽤用側では、
⼈人がAPIIFドキュメントを読んで
理理解して、
それを利利⽤用する処理理を実装する。
「仕様/ドキュメント/実装」を⼈人の
脳が頑張って変換しながら何種類も成
果物つくってるが・・・。
この変換時の認識識違いによるズレは結
合試験まで発⾒見見できない。
そこで導⼊入したのが
インタフェース
定義⾔言語
当初はOSSの以下あたりを使えない
かと検証したが、
• Googole ProtocolBuffer
• Apache  Thrift
以下の理理由から断念念
• バリデーションが表現できない
• ドキュメントが⽣生成できない
イチから以下を⾏行行いました。
• ⾔言語⾃自体の⽂文法設計
• コンパイラ開発
• Javaコードジェネレータ
• ドキュメントジェネレータ
• ドキュメント表⽰示サーバー
IDLでAPIIF設計を
すると・・・
1.APIIFをIDL⽂文法で記述
記述した内容はIDLコンパイラを通
すことで整合性チェックを⾏行行うこと
が出来る。
2.ドキュメント⽣生成して公開
ドキュメントジェネレータを通して
JSONデータを⽣生成し、それをドキュ
メント表⽰示サーバーにアップロード
して公開する。
3.Javaコード⽣生成して利利⽤用
Javaコードジェネレータを通して
実装で利利⽤用する。⽣生成コードは編集
は⼀一切切せず利利⽤用のみとして、常に上
書き更更新可能にしている。
振り返っての所感
問題検出をできるだけ
前⼯工程にもってくる
システム開発は先の⼯工程(結合試
験とか総合試験とか)に進めば進
むほどソースコードが開発者の⼿手
元を離離れる。
そこで⾒見見つかった問題を修正して
環境に届けるには相応の時間がか
かる。
後で発覚した問題への対応コスト
はただただ増していくばかり。
IFを結合前に安定させられるIDL、
コンパイルと型宣⾔言、テストを書
いて⼿手元でもCI/CDの中でも動か
して、最後にCoverityの静的解析。
開発者の⼿手元を離離れる直前までや
れることをやる。
テストできないと⾔言い
訳される要因を潰す
テストに向かないアーキテクチャ
や道具を使うと対応コストを理理由
にやらないことが正当化されやす
い。
アプリケーションアーキテクチャ
まで踏み込んでテストを考慮する
ことができればベスト。
ただこれが出来るのはかなり幸運
なこと。
テストできない理理由が消えると、エ
ンジニアはわりとちゃんとテスト書
くようになる。
書いたほうがいいのは皆わかってる
し、書いたものは皆動かしたい。
未熟でも早め適⽤用/
フィードバック受けて
磨く
やり⽅方を⼤大幅に変えた時は最初から
いろいろ頑張りたくなる。
でもそこは⼀一旦最低限に押さえて早
く実戦投⼊入することが⼤大事。
机上で考えるよりも実践の場で揉
まれるほうが⼀一番早い。
ただし最初の適⽤用プロダクトでは
途中で⾊色々と⽅方針変更更が⼊入りがち。
それを承知してもらうのと、でき
ればしがらみの無い新規プロダク
トがベスト。
部⾨門⻑⾧長サポートと
仲間が⼤大事
現場で「こうしたい」という思いが
あっても、仕事で開発をしている以
上はビジネス要件への対応は⼀一番⼒力力
を割くべきところ。
部⾨門⻑⾧長や組織が
• 攻めの開発(ビジネス要件対応)
• 守りの開発(保守/刷新)
の両⽅方を理理解してくれて初めて効果的
に取り組める。
個⼈人の能⼒力力とアウトプットはかなり
限られる。
仲間がいれば、個の範囲を越えた成
果が必ず出る。

STAC2015 講演3 広告システム刷新よもやま話〜テストが当たり前となるまでにやったこと #stac2015