読者です 読者をやめる 読者になる 読者になる

テストを使いサービス開発を駆動していくために取り組んでいること

技術部の松尾(@Kazu_cocoa)です。

最近、 @moroや私を中心に、テストから開発を駆動するという方向で、とある活動を始めました。その活動の中では、 @t_wadaさん技術顧問 として巻き込んで活動を進めています。そんな取り組みを少しここにまとめます。

f:id:kazucocoa:20160301195056j:plain:w300

取り組みの前段階

先日、私はテストエンジニアというロールに焦点を当ててテストという言葉に対する2種類の話をいたしました。TDDのようにテストによって開発を駆動していく側面の話と、人の認知・感じ方に寄った仕様自体含めてテストしていく側面の話です。

その際、会の傍でt_wadaさんらと私たちが開発するWebアプリケーションのテストコードに関して少し話をしていました。

現在、クックパッドのWebアプリケーションには2万を超えるRSpecのテストケース(example)が存在します。すべてのテストを単純に実行すると、完了までに相当に長い時間を必要とします。そのため、『分散テスト実行システムRRRSpecをリリースしました』などを始めとした取り組みを行い、現実的な時間でテストコードが完了する、という状態を維持してきました。ここでいう現実的な時間とは、過去、JaSST'14 Tohokuにて高井がお話ししたCIにかかる時間は10分以内、多くとも20分以内にしていることをさします。(スライドはこちら)

これにより、テストコードとその開発プロセスによって、巨大なクックパッドのWebアプリケーションが壊れにくいように開発を続けることをある程度担保してきていました。一方で、その実行時間や依存関係、テストコードの巨大さは日々のサービス開発に伴って増加の一途をたどっています。ある機能を確認するための重複したテストコードや時間などに依存するコードのような依存性の高いコードなど、保守を困難にさせるコードも散見されます。これにより、テストが不安定になって開発に影響を与えることもありました。

別な取り組みとして、分割が進むサービスに対する依存性問題に対して『サービス分割時の複雑性に対処する: テスト戦略の話』や『マイクロサービス時代を乗り越えるために、Rack::VCRでらくらくアプリケーション間テスト』といったものも進めています。これは、クックパッド社内で取り組んでいるサービスの分割を進めると直面していくであろう課題に対して、私たちの環境ではどのような解決策が現実的なのか、という試行錯誤になります。

本来、私たちはサービス開発を続け、価値あるものを創造し続けたいという想いを持っています。そのために、テスト(コード)をよりうまく使うことに対して力を使い、サービス開発に力を注いでいきたいと考えていました。そのためにはやはりテスト(コード)は開発の中で必要な手段であり、さらにはその手段の価値を引き出すために テストが開発を駆動する という側面にもちゃんと取り組む必要性があるのではと考え、t_wadaさんをその方面の技術顧問として巻き込み、アドバイスを頂きながら変化する道を歩み始めました。

テストが開発を駆動する世界

テストが開発を駆動する世界の1面として、例えばテストしやすい設計にするというものがあります。これは、奇怪なことをしなくとも単純な形で対象となる機能の動作を確認しやすくする、というようなものです。多くの場合、そうすることで開発を継続していくなかでも動作を確認しやすいコードを保てる可能性が高くなります。

このなかで、社内でとある時間に依存するコードに対してあるやりとりがありました。その世界の実現に向けて実際にどのようなやりとりが行われているのか想像できる例の一つです。

時間の依存性を除く

Rubyだと、timecopといった時間を操作するgemがあります。一般に、リファクタリング系の書籍やテストに関する書籍などにおいて、時間に対する依存性はテストを困難にする要因の1つであると広く知られています。私たちの間でも、過去に時間に対する依存性に関して議論が起こりましたが、当時の現実解としてtimecopを使うようになりました。

その依存性に対して、最近またある議論が行なわれました。依存性を除いたテストを構築するにあたり、現在のクックパッドの開発に沿うように導入するにはどうすれば良いか、というような議論です。

f:id:kazucocoa:20160301195018j:plain

f:id:kazucocoa:20160301195045j:plain

このようなテストしやすい形からシステムを考え、それを実現していくというのはテストが開発を駆動する、という一例です。この取り組みの結果、後になっても時間が関係する機能の組み合わせに対する動作確認を容易にできるようになると、サービス開発を行う場合も極端に臆病にならずに様々な取り組み、確認、リリース、検証というサイクルを、障害を予防したうえで回すことができるようになってきます。

JSのテストを書くための、実例を使った社内勉強会

t_wadaさんに技術顧問をやっていただいている中で、テストに関する社内勉強会をやる機会がありました。先日行った、ある社内勉強会のことです。

最近、私たちのサービスの1つである『おいしい健康』がリニューアルされました。このサービスは、以前に『もう失敗しない!プロジェクト書きなおして、最高の開発環境を手に入れる』というタイトルでtechlifeにも書かれたサービスです。このサービスの実コードを題材に、JavaScriptのテストを書くための勉強会を行いました。

f:id:kazucocoa:20160301195101j:plain

健康チームはサービスとして1つのRailsアプリに分離しましたが、そのテストコード、特にJavaScriptに対するテストの書き方に悩んでいました。愚直にSeleniumなどを使いE2Eのようなテストを実施した場合、そのテスト実行時間や安定性、ロジックに対する網羅性が損なわれるのは明らかです。JavaScript界隈自体動きが早いので、ロジックに近いところのテストを十分に満たすには、その動きにも十分に追従する必要がありました。そのような動きの早いJavaScript界隈の話に追従しつつテストの形を考え、さらにはサービス開発を同時に進めるということは負荷のかかりすぎるものでした。

そこで、JavaScriptに関しては知見も豊富なt_wadaさんに協力してもらい、主にはJavaScriptを対象としながらもテストコードに対する勉強会を開きました。結果的には、約20名のエンジニアに参加してもらい、約2時間にわたり学び、議論し、理解を深めることができました。

JavaScript界隈におけるテストツールの分類であったり、そもそものテストコードの区分やその独立性などの基礎的なところといった、比較的幅広い話をしていただきました。例えば、以下のサイボウズの佐藤鉄平さんの33枚目の資料を参考にしながら、JavaScript エコシステム、テストツールの種類や機能、守備範囲をお話いただきました。

また、それに付随して以下の通りいくつか関連するURLを補足、共有いただいたりしました。

f:id:kazucocoa:20160301195051j:plain

サービスを分割した直後はテストコードも小さく、比較的高速に開発を回すことができます。一方で、何も考えずにテストコードを書いていくと防ぎたい不具合も防げなかったり、時間がかかりすぎるものになったりします。そんな課題に対して、このような勉強会という形で目指すべき姿やその取り組みの具体例を共有し、開発を支える、といった取り組みも行っています。

健康チームは足がかりを得ることができたらしく、手を出すことができるところから少しずつ学んだことを生かそうとしています。

テストのパターンを学ぶ

テストコードを書く人に取って著名な書籍にxUnit Test Patternsがあります。社内勉強会で、こちらの書籍を学びながら、JavaScriptだけではなく、Android/iOS/Rubyなど問わず、どういうパターンがあるのかを学ぼうと考えています。

http://xunitpatterns.com/Cover-Small.gif

通常の設計に対するデザインパターンのように、テストコードにも同様のパターンが存在します。それらを活用することで、よりサービス開発に注力できるだけの基礎力がついてくると思っています。

これから

今後、このような活動や実際にテストコードを書くなどして、今あるテスト(コード)の課題を解決していきながらも、同じ失敗を予防できるような形を作り上げたいと思っています。これにより、先のエンジニアトークナイトで話したことがあるように、副次的にでもサービス開発を支えたり、サービスの品質を向上するためのより良い手段としていきたいと考えています。

私たちは、このような取り組みをより深化させるために仲間を募集しています。特に、ソフトウェアエンジニアとしてテストによって開発を駆動させたい人や、より品質を高めるための組織を築き上げることに興味のある人、いかがでしょうか。

/* */ @import "/css/theme/report/report.css"; /* */ /* */ body{ background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527163350.png'); background-repeat: repeat-x; background-color:transparent; background-attachment: scroll; background-position: left top;} /* */ body{ border-top: 3px solid orange; color: #3c3c3c; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; line-height: 1.8; font-size: 16px; } a { text-decoration: underline; color: #693e1c; } a:hover { color: #80400e; text-decoration: underline; } .entry-title a{ color: rgb(176, 108, 28); cursor: auto; display: inline; font-family: 'Helvetica Neue', Helvetica, 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', Meiryo, Osaka, 'MS Pゴシック', sans-serif; font-size: 30px; font-weight: bold; height: auto; line-height: 40.5px; text-decoration: underline solid rgb(176, 108, 28); width: auto; line-height: 1.35; } .date a { color: #9b8b6c; font-size: 14px; text-decoration: none; font-weight: normal; } .urllist-title-link { font-size: 14px; } /* Recent Entries */ .recent-entries a{ color: #693e1c; } .recent-entries a:visited { color: #4d2200; text-decoration: none; } .hatena-module-recent-entries li { padding-bottom: 8px; border-bottom-width: 0px; } /*Widget*/ .hatena-module-body li { list-style-type: circle; } .hatena-module-body a{ text-decoration: none; } .hatena-module-body a:hover{ text-decoration: underline; } /* Widget name */ .hatena-module-title, .hatena-module-title a{ color: #b06c1c; margin-top: 20px; margin-bottom: 7px; } /* work frame*/ #container { width: 970px; text-align: center; margin: 0 auto; background: transparent; padding: 0 30px; } #wrapper { float: left; overflow: hidden; width: 660px; } #box2 { width: 240px; float: right; font-size: 14px; word-wrap: break-word; } /*#blog-title-inner{*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-position: left 0px;*/ /*}*/ /*.header-image-only #blog-title-inner {*/ /*background-repeat: no-repeat;*/ /*position: relative;*/ /*height: 200px;*/ /*display: none;*/ /*}*/ /*#blog-title {*/ /*margin-top: 3px;*/ /*height: 125px;*/ /*background-image: url('http://cdn-ak.f.st-hatena.com/images/fotolife/c/cookpadtech/20140527/20140527172848.png');*/ /*background-repeat: no-repeat;*/ /*background-position: left 0px;*/ /*}*/