TDD programming software 研修

t_wadaさんによるTDDワークショップを開催しました

TDD programming software 研修

TDDワークショップの開催

こんにちは。EC事業部エンジニアの@symmondsです。 2021年の10月に入社しEC事業部のCREチームで活動しています。詳しい活動内容については以前に記事を書いたので、興味がある方はぜひ読んでみてください。

GMO ペパボ(以下、ペパボ)では2021年に引き続き、今年も日本のTest Driven Development(TDD) の第一人者である @t_wada さんをお招きしてワークショップを開催しました。

「なぜTDDワークショップを開催したのか?」については、去年のTDDワークショップの記事で詳しく説明していますので、こちらをご覧ください。

研修内容

ここからはホスティング事業部の@matsusukeがご説明します。
2022年の3月にペパボに中途入社しました。最近はFall Guysにはまっていますが未だに一位が取れず、夜しか眠れません。

今回、TDDワークショップに1日参加した内容と学んだことを記述します。

研修概要

今年のTDDワークショップは、新卒12期エンジニアの社内研修プログラムの一環として開催されました。
それ以外に、ペパボ各事業部内でテストを書く文化を広げていくために、2021年10月以降に中途入社したパートナーである@matsusuke@symmonds@yanagiの3名が有志として参加しました。

研修スケジュール

  • 10:00 〜 12:00 講演 + ライブコーディングによるデモ + 質疑応答
  • 12:00 〜 13:00 昼休み
  • 13:00 〜 16:30 ワークショップ + 1on1コードレビュー
  • 16:30 〜 17:00 全体レビュー + 質疑応答 + クロージング

ライブコーディングによるデモ

TDD Boot Camp 2020 Online #1で行われた基調講演/ライブコーディングの動画を事前に予習し、研修では動画が終わった時点のコードからスタートし、それを元にした議論から研修が始まりました。

動画で用いた FizzBuzz 問題とTODOリストの例は以下の内容です。

要件:
1から100までの数をプリントするプログラムを書け。
ただし3の倍数のときは数の代わりに「Fizz」と、5の倍数のときは「Buzz」とプリントし、3と5両方の倍数の場合には「FizzBuzzとプリントすること」
Fizz Buzz 数列とその変換規則を扱う FizzBuzz クラス
  convertメソッドは引数に与えられた整数を文字列に変換する
    3の倍数のときは数の代わりにFizzに変換する
      同値類の中の最小の3の倍数3を渡すと文字列Fizzを返す
      上の境界値のひとつ内側の値であり同値類の中の最大の3の倍数99を渡すと文字列Fizzを返す
    5の倍数のときは数の代わりにBuzzに変換する
      同値類の中の最小の5の倍数5を渡すと文字列Buzzを返す
      上の境界値であり同値類の中の最大の5の倍数100を渡すと文字列Buzzを返す
    3と5両方の倍数のときは数の代わりにFizzBuzzに変換する
      同値類の中の最小の3と5の公倍数15を渡すと文字列FizzBuzzを返す
      下の境界値のひとつ外側の値0は3と5両方の倍数でもあるので0を渡すと文字列FizzBuzzを返す
    その他の数のときは数をそのまま文字列に変換する
      下の境界値1を渡すと文字列1を返す
      下の境界値のひとつ内側の値2を渡すと文字列2を返す
      上の境界値のひとつ外側の値101を渡すと文字列101を返す

模範解答のサンプルコードはこちらにあります。

基調講演/ライブコーディングの動画でも説明をしてくださっていますが、TDDのスキルとして重要な下記の要素についてライブコーディングによって学ぶことができました。

特に、「テストの構造化とリファクタリング」の項目については、仕様を知らない新人エンジニアがFizzBuzz問題のテストコードを初めてみたときを例に、
「テストコードを見ただけでプロダクションコードの仕様を読み取ることができるか?」といった問題への取り組みについてより詳しく説明してくださいました。
複数人で開発すると「テストコードは書いた本人に聞かないと仕様がわからない」といった問題が出てきます。テストの実行結果から仕様を読み取れることができれば、書いた本人がどういう意図でテストケースを書いたかがわかり、コードのメンテナンス性が向上します。「コードのメンテナンス性を高めるためにテストをどう構造化し、リファクタリングしていくか」をライブコーディングで学ぶことができました。

ワークショップ + 1on1コードレビュー

午後からは新しい演習問題を題材に、TDDを用いて開発に各自取り組みました。
演習問題は、「整数の閉区間」をテーマに、問題難易度が易しいもの・難しいもの2種類があり、TDDを進めていく上で必要な「問題の細分化」を自分で行うもの・あらかじめTODOリストとして準備されているものがそれぞれ2種類の、合計4種類の中から自分で問題を選ぶ、という内容でした。
問題文はこちらです。

私は、難易度が難しく、自分で問題を細分化する課題に挑戦しました。
演習の途中で数回@t_wada さんに1on1でコードレビューをしていただき、アドバイスや疑問点の解消を行なっていただきました。
1on1については他の参加者全員の前で行うものでしたので、他の参加者に対しても質問をしてみたり、アドバイスを行ったりといったアクションも見られました。

演習問題への取り組み

演習問題は各自好きなプログラミング言語で参加して良い、というルールでしたので、私はGo言語を選択しました。 実装を進めるにあたり、まず初めに午前のライブコーディングで学んだ方法を元にTODOリストに落とし込みながら仕様を理解することにしました。 TODOリストを整理しながら「どういった構造体が必要か」「どの実装から書いていくべきか」「仕様確認漏れがないか」などを考えていきました。以下が、私が作成したTODOリストです。

## 問題文
問題文
整数閉区間を示すクラス(あるいは構造体)をつくりたい。
整数閉区間オブジェクトは下端点と上端点を持ち、文字列表現も返せる(例: 下端点 3, 上端点 8 の整数閉区間の文字列表記は "[3,8]")。
ただし、上端点より下端点が大きい閉区間を作ることはできない。
整数の閉区間は指定した整数を含むかどうかを判定できる。
また、別の閉区間と等価かどうかや、完全に含まれるかどうかも判定できる。
================================================================
[] 整数閉区間を示す構造体を作る
  [] 整数閉区間オブジェクトは下端点と上端点を持つ
  [] 文字列表現も返せる(例: 下端点 3, 上端点 8 の整数閉区間の文字列表記は "[3,8]")。
  [] 下端点・上端点を指定すると、それぞれをプロパティにもつ構造体を取得する
  [] 下端点・上端点を指定すると、それを文字列表現として返すプロパティも持つ

[] 上端点より下端点が大きい閉区間を作ることはできない。
  [] 上端点より大きい下端点を指定した場合、errorを返す

[] 整数の閉区間は指定した整数を含むかどうかを判定できる。
  [] 閉区間内の整数の場合true
  [] 整数が下端点と一致する場合true
  [] 整数が上端点と一致する場合true
  [] 整数が下端点よりも小さい場合false
  [] 整数が上端点よりも大きい場合false

[] 閉区間Aが別の閉区間Bと等価かどうかを判定できる
  [] 一致している場合true
  [] 一致していない場合false

[ ] 閉区間Aが閉区間Bに完全に含まれるかどうかを判定できる
    [] Aの下端点がBの下端点以上かつAの上端点がBの上端点以下の場合、true
    [] Aの下端点がBの下端点未満の場合、false
    [] Aの上端点がBの上端点より大きい場合、false

TODOリストを整理しながらまずは1つの内容に絞ってテストを書き、次にそのテストを最小限に満たすコードを書きます。
その後、実装コードをリファクタリングする、といった内容でコーディングを進めていきました。
課題を進める上で、TDDは単に「テストを先に書いて、それが通る実装を書く」という手法ではなく、「テストを書く→テストコードを満たす実装を書く→リファクタリング」のサイクルを回していくことでより良い設計に変えながら実装を進めていく手法なのだな、ということを体感することができました。

具体的には、「閉区間Aが別の閉区間Bと等価かどうかを判定できる」という仕様を満たすコードとして、最初の実装では「閉区間構造体A・Bを引数にとって等価かどうかを判定する関数」としていましたが、リファクタリングを行うことで、「閉区間A構造体をレシーバとして、構造体Bを引数にとって等価かどうかを判定する関数」に設計を変えることができました。

ワークショップの感想

ここからは2022年3月に中途入社したminnne事業部エンジニアの@yanagiが説明します。最近は苦手意識のあったパクチーが食べられるようになってから、タイ料理やシンガポール料理などのアジアン料理に興味が湧いているところです。

@matsusuke @yanagi @symmonds の3名が今回のワークショップに参加した感想を記載します。

matsusuke

Go言語を選択して参加したため、RubyにおけるRSpecの「describeやcontext」で表現できるテストの階層構造化の手段がGoのtestingパッケージでどのように実現すればいいかわからず、カスタム関数を自作することでそれを表現しました。

課題への取り組み終了後、参加者全員が参加する全体レビューの時間で@t_wadaさんより私のコードのレビューを行っていただきました。その中で、Go言語におけるテストの構造化のためのコードの書き方のアドバイスやサンプルコードの紹介をしていただきました。レビューを通して、より実践的なアドバイスをいただくことができ、とても貴重な経験ができました。
サンプルコードでは、Go言語のTable-driven testsの手法を紹介していただきました。

また、本ワークショップ終了後、早速実務のGoのコードについて、テストの構造化に着手することができました。今後も学んだことを積極的に実務にも取り入れていきたいと思います。

yanagi

事前に視聴をしていた動画や午前中のライブコーディングでTDDのやり方を理解したつもりになっていましたが、実際に午後のワークショップで手を動かしてみると思った以上にどう進めればいいか分からない・悩む場面が多いことに気づきました。今回の研修の冒頭で@t_wada さんが「(TDDに限らず)何かを理解しようとする時は手を動かしてから理解する方が効率がいい」とおっしゃっていた意味を理解しました。今後も何かを見聞きしただけで理解した気にならず、今回のワークショップのように手を動かしてみることを心がけていきたいです。

また、今回の研修ではTDDの理解を深めただけでなく、@t_wadaさんに1on1でコードレビューをしていただいたり他の参加者のコードを見てコメントし合ったりと、TDDのやり方やお題の実装について議論できたのがとてもいい体験になりました。今回のワークショップにとどまらず、実務でもTDDでのペアプロを取り入れてチーム内で仕様や設計・実装の議論を増やしていきたいと思っています。

symmonds

テストを書く→テストが通るコードを書く→リファクタリングを行うというTDDの大まかな流れを、課題を通じて体験することができました。

テストコードを書き始める前に「目標をTODOリストに書き出す」ことを行いました。先にTODOリストをつくることで、仕様を満たすための要素を細かく整理でき、問題を整理すると解決もスムーズになると体感しました。この手法は実務にも取り入れて活用していきたいです。

また、1on1のレビューでは自分のテストコードのレビューはもちろん勉強になったのですが、他の人の書いたテストコードやレビュー内容から学べることが多くありました。特に「テストをどう構造化したのか」「テストをどこまで網羅しているのか」は自分だけでは気づけないことがあったので参考になりました。

t_wadaさんからのコメント

事前に動画で予習いただく反転学習の要素を取り入れた研修を設計したため、応用的な内容の講義から始めることができました。 また、当日は研修用の Slack チャネルを作成いただき、リアルタイムに質疑応答したり、研修の進み具合を各自実況したりと、オンライン研修ならではの進め方もスムーズでした。 ご参加くださいました皆様には、リファクタリングはプロダクトコードだけでなくテストコードに対しても常に行うこと、テストコードを生きた詳細設計とすることなどを体験いただけたのではないかと考えています。 今回の研修が、今後皆様がテストと共に開発していくキャリアを築いていくきっかけになりましたら幸いです。

最後に

研修終了後、@t_wada さんに研修後の推薦図書を挙げていただきました。 今回の研修で学んだTDDやテストの重要性は、推薦図書の本を読むことや実際にTDDで開発をすることでより理解を深め、さらにテストコードを書く・チームでテストについて議論する・TDDでペアプロやモブプロをするといったアクションを通して業務に落とし込んでいけるのではないでしょうか。

今回テックブログを書いたのは中途入社の3人でしたが、今回一緒に研修を受けた新卒のメンバーとともに社内でテストを書く文化やTDDを盛り上げていきたいと思います!