コドモン Product Team Blog

株式会社コドモンの開発チームで運営しているブログです。エンジニアやPdMメンバーが、プロダクトや技術やチームについて発信します!

ATDD(受け入れテスト駆動開発)でプロダクトを作っての学び

こちらはコドモン Advent Calendar 2022の16日目の記事です🎄

こんにちは!コドモンプロダクト開発部の加藤です。最近初めてカヌレを食べたのですが、すごく美味しくて感動しました。寒くて元気が出ない日も増えてきましたが、おいしいものを楽しみつつ冬を乗り切りたいです🍊

今回はATDD(受け入れテスト駆動開発)でプロダクトを作った体験から学んだことをまとめます。

ATDD(受け入れテスト駆動開発)とは

ATDDとはどういった開発手法なのかについて紹介します。

「ATDD」は「Acceptance Test Driven Development」の略で「Acceptance Test(受け入れテスト)」を中心に開発を進めていきます。

開発者はソースコードの作成より先に、ユーザーの視点から見たシステムのふるまいを表現した受け入れテストを作成します。この時点では実装が存在しないので作成したテストは必ず失敗します。この後の開発は実装・テスト実行のサイクルを繰り返し、作成した受け入れテストが成功したら完了となります。

受け入れテストを通す過程で、ユニットテストの作成・実装・リファクタリングのサイクルを回しながら開発を進めていきます。ユニットレベルのテストで上記サイクルを回すことをTDD(テスト駆動開発)と呼びますが、ここでは細かい説明を割愛します。ATDDはTDDと組み合わせてテストの結果から頻繁にフィードバックを得ながら開発を進めていく手法といえます。

図で表すと下記のとおりです。

ATDDのサイクル*1

実際にやったこと

具体的な内容が想像しやすくなるようにチームの規模感や実際に行っていた内容を紹介します。

開発内容

巨大なモノリスから、機能を切り出して新アーキテクチャで作るリプレイス

チームの規模

エンジニア4名、QAエンジニア1名、デザイナー1名、プロダクトマネージャー1名

手順

ひとつのユーザーストーリーを実現する手順は以下のとおりです(内容はサンプルです)。

自動テストフレームワークとしてGauge、ブラウザ自動操作テストツールとしてPlaywrightを使用しました。

1. ユーザーストーリーの受け入れ条件をspecファイルに書き起こす

## 資料詳細画面で資料タイトルを見ることができる
* テスト園でログインする
* 資料室アイコンをクリックする
* 資料室の一覧画面から資料"園だより"を選択する
* 詳細画面の資料タイトルに"園だより"と表示されていること

2. テストを実装する

UIがない状態で先にテストを実装します。

テスト対象要素のdata-*属性の名前を先に与えることで、UIがない状態でのテスト実装を実現しています。
ここで与えたdata-*属性と同じものを後の実装(手順4)でテスト対象要素に付与します。

import { Step } from "gauge-ts";

@Step("詳細画面の資料タイトルに<title>と表示されていること")
public async displayTitle(title: string) {
  const actual = await page.locator("[data-test=title]").innerText();
  assert.deepEqual(actual, title)
}

// 他にも必要な分テストを実装
// ....

3. Gaugeを実行し、テストが失敗することを確認する

gauge run specs/example.spec

4. プロダクトコードを実装する

詳細は割愛しますが、ユニットテストを作成してTDDのサイクルを回しながら実装を進めます。

5. Gaugeのテストを実行する(手順3と同じ)

6. テストが成功したらこのユーザーストーリーの実装完了。失敗した場合は成功するまで4・5の手順を繰り返す。

実践して感じたメリット

目指すべき挙動を最初に明確にできる

受け入れテスト作成の段階で仕様の不明点があった場合はプロダクトマネージャーなどとコミュニケーションをとって 完成形の状態を確認してから再度テスト作成・実装に戻っていました。 早い段階で意図した仕様とのずれを調整する機会になるので、軌道修正までにかかる時間のムダを減らすことができました。

デグレを心配せず、内部構造の変更ができる

開発が進むごとにE2Eテストが増えていきます。その全テストをCIで実行することで、デグレが起きていないことを確認しながら安全にリリースすることができました。実際に、開発中に発生したデグレをE2Eで検知したこともあり、ユーザーへの不具合流出を防げました。複数人で開発を進める中、どんどんプロダクトの規模も大きくなってくるので、網羅的なE2Eテストでデグレが起きていないことを確認しながら開発を進められるのは安心感がありました。内部構造も変更しやすいので、リファクタをしながら堅牢なプロダクトを目指すことができました。

注意が必要だと感じたこと

E2Eの実行時間が長くなりすぎないように工夫する

E2Eテストはユニットテストなどと比べて実行時間が長いという特徴があります。 ATDDではテスト失敗というフィードバックを受けてすぐに修正にとりかかるのが理想ですが、テストの実行時間が長いとそのサイクルが回るのも遅くなってしまいます。私たちのプロダクトでもE2Eテストが増えたときにすべて実行が終わるまでに20〜30分かかるという状況が実際に発生して、対策をする必要がありました。

対策としては以下が考えられます。

  • E2Eテストの並列化

  • 実行時間が比較的短いテスト(ユニットテスト・結合テスト)で保証する範囲を増やし、E2Eのみに依存しない状態を作る

E2Eの安定性を高めるために工夫する

E2Eテストは実行が不安定になりやすいという特徴があります (ここでいう不安定とはテスト対象のシステム側の不具合はないが、テストがなんらかの理由で失敗することを指します)。不安定なテストが大量にあるとそのテストを修正するために時間がかかってしまい、本来開発に割けたはずの時間が奪われてしまいます。

対策としては以下が考えられます。

  • 画面上の要素が現れるのを適切に待てるようにテストを実装する
    • (例:「n秒待ったらクリック」ではなく「〇〇の要素が現れたらクリック」とする)
  • リトライ回数を設定して、複数回実行を試すようにする
    • (例:gaugeのコマンドで実現する場合 gauge run --max-retries-count=2)
  • E2Eの不安定さが改善できない部分は手動テストも含め、別の方法で動作を保証する(無理にE2Eでなんとかしようとしない)

E2Eの信頼性を高めるために工夫する

不具合を適切に検知するためにも、E2Eテストは常に信頼できる結果を返す必要があります。 「テストがOKだったので不具合はないと思っていたが、テスト自体が正しくなかった」という悲しい事態は避けなければなりません。

対策としては以下が考えられます。

  • (当たり前ですが)開発した部分の手動テストも必ず行う

  • 不正なデータをE2Eテストの期待値に設定したとき、テストが正しく失敗することを確認する

  • 仕様確認・テスト実装など一連の流れをペアで実施し、常に互いにレビューし合いながら作業する

おわりに

私は今回初めてATDDを実践したのですが、安定したプロダクトリリースを支援してくれる開発手法だと感じました。 業務レベルのプロダクト規模ならではの悩み(テスト数が増えて実行時間が長くなることなど)も体感できたのが大きな学びとなりました。

ATDDや安定したプロダクトリリースに興味がある方に向けて、この取り組みが参考になれば幸いです。

ここまで読んでいただきありがとうございました!明日以降も引き続きコドモンのアドベントカレンダーをお楽しみください🎅🎁

*1:実践テスト駆動開発 p.44より