ペアプロ × テスト駆動開発 で若手をユニットテストに目覚めさせる

はじめまして。エンターキーを叩く音が日に日に大きくなってゆく山本です。

さて「テスト駆動開発 (Test-Driven Development)」とは、なんて難しそうな響きでしょうか。

私も新卒入社3年目の春を控え、開発手法のひとつふたつ学びたいところですが、なかなか参考書に手が伸びません。

そんな小難しい単語を聞くと下を向いてしまうシャイガールのために、こちらの記事を書かれた金谷さんが社内勉強会を開いてくださいました。

参加者で即席コンビを組み、テスト駆動開発(以下TDD)でお題に挑戦するペアプログラミング大会、題して【TDD Boot Camp】 。厳格そうなタイトルですが大いに盛り上がり、私は近くで働いていたチームメンバーに「参加すればよかった」と言わしめる幸せオーラを放って帰還しました。とても実りある5時間でした。あんなに敬遠していた TDD のことがお気に入りになり、テストコードを書くことが苦痛でなくなりました。

というわけで、ペアプロ × TDD の良さを知って頂きたくこの記事を書きました。

トランプゲームを作ろう!

ある平日の午後1時。男女半々、ベテランから若手まで個性豊かな4名が会議室に集まりました。

お題は【ブラックジャック】。まずは主催者さんよりシブいトランプを使ったゲーム説明がされます。

f:id:monotaro_yamamoto:20190307171730j:plain

シブすぎて集中できません。

今回作るブラックジャックは、ジョーカー無し52枚の山札から、プレイヤー(実行者)とディーラー(CPU)が1枚ずつカードを引いていく動作がメインになるとのこと。正しく動くブラックジャック・プログラムの第一歩は、カードの山札を作ることだと言えるでしょう。

しかし不安が湧き上がります。山札オブジェクトを作れたとして、シャッフルはどう実装する? プレイヤーとディーラーの持ち札はどう保存する? 一体どこから手を付ければ……

ゲーム説明を聞いただけで私はオロオロしています。でも大丈夫。TDD のサイクルに身を任せれば、難しそうなお題もたやすく紐解ける!

TDDってなんなの?

TDDとは、ざっくり言うと「動作するきれいなコード」を目指して短い工程を繰り返す開発手法です。

汚いし動かないコード」を出発点とし、
汚いけど動作するコード」を経由して、
動作するきれいなコード」を目指します。

f:id:monotaro_yamamoto:20190228182551p:plain

短い工程を繰り返す。いい響きですね。ランニングの「あの曲がり角までは頑張って走ろう」のように、短いゴール地点が常に見えているならモチベーション維持になって良さそうです。元気が出てきました。

開発は流れに乗るだけ

2組のペアがそれぞれのデスクへ移動し、開発フェイズ開始です。

ではTDDの開発工程を説明しがてら、実際に私たち(こちらの記事の芝本さんとペアを組みました)が書いたソースコードを見ていきましょう。

① 動かない unittest を書く。

カード山札のクラスオブジェクト deck を作り、その枚数が52枚だといいなあというテストです。

汚いし動かないコード」

class TestDeck(unittest.TestCase):
 
    def test_create(self):
        from blackjack.card import Deck
        deck = Deck()
        self.assertEqual(52, deck.remain())

もちろん、実装は何も作ってないので動きません。

ImportError: cannot import name Deck


② 多少強引でもいいので unittest が動くことを目指す。

カード山札のクラス Deck を作りました。カードのマーク・数字を考慮せず、まずは [0, 1, 2, 3 .... 51] というただ数字だけが入ったリストをコンストラクタで生成することにしました。

汚いけど動作するコード」

class Deck(object):
    def __init__(self):
        self.cards = []
        for n in range(52):
            self.cards.append(n)
 
    def remain(self):
        u"""山札に残っている枚数を返却する"""
        return len(self.cards)

すると、TestDeck がパスするようになりました。あまりに強引ですが、とにかくテストが通りました。私もパートナーさんも「これでいいの!?」と半信半疑でしたが、主催者さんは大きくうなずきます。最初はこれでいいんです。

確かに、動作だけは保証されているからこそ、きれいにすべき所がはっきりと浮かび上がってきますね。次に進む道が、もう明るく見えています。

③ unittest が動く状態を維持しつつ、リファクタリングをする。

ついでに Card クラスを作ってきました。

Card.SymbolList には["ハート", "ダイヤ", "クラブ", "スペード"]が定義されています。

まだまだ改良点はありますが、カードのマーク・数字を組み合わせて [(ハートの1), (ハートの2), (ハートの3), .... (スペードの13)] のリストにレベルアップさせることができました。

動作するきれいなコード」

class Deck(object):
    def __init__(self):
        self.cards = []
        for number in range(1, 14):
            for symbol in Card.SymbolList:
                self.cards.append(Card(symbol, number))
 
    def remain(self):
        return len(self.cards)

あとはこれの繰り返しです。

このあと私たちは満足するまで Deck クラスをリファクタリングし、次にCard クラスのテストコードを書き、Card クラスをざっくり書いて……という流れにまた乗りました。

f:id:monotaro_yamamoto:20190306155348p:plain

これが TDD サイクルです。

① 動かない unittest を書く。 => テスト失敗を表す〝RED〟

② 多少強引でもいいので unittest が動くことを目指す。 => テスト成功を表す〝GREEN〟

③ unittest が動く状態を維持しつつ、リファクタリングをする。 => 〝REFACTORING〟

流れに身を任せるだけでどんどんコードが完成に近づく心地よさと安心感は計り知れませんでした。

参加した感想

使った時間は、テスト作成(設計で悩む時間はこちらに含む) : コーディング = 7 : 3程度。

★面白いと思ったこと:

  • 動かないものから動くものへ、というサイクルが決まっているところ。普段は unittest が失敗するとかなり焦りますが、TDD だと考えると前進している感が得られるのでポジティブになれました。動かなかったテストが初めて通った瞬間の達成感は、思わずパートナーさんとハイタッチするほど。
  • テストを書くときに自然とメソッドの全体図を俯瞰できるので、何が必要そうか、どこに悩み所があるか事前に分かるところ(逆に、テストを書き始めた段階でかなり先の落とし穴を察してしまい手が止まる、ということもありましたが…)
  • 悩み終えてからプロダクションコードに入るので、そちらはスムーズに無駄なく書けることが多かったです。


★難しいと思ったこと:

  • 「汚くて動かない」から「汚いけど動く」にするのは簡単ですが、そこから「きれいで動く」にするのは壁が高かったです。真の綺麗さを求め始めると、他のメソッドやクラスの設計にも粗を見つけてしまい、あれもこれもと修正に手を出し始めて収拾がつかなくなったり、そのせいで逆に既存の unittest が壊れたりして大惨事になりかけました。おそらくクラスを作る順番がビミョウだったのでしょう。トップダウンとは言わずとも、ある程度全体設計と順序を相談して決めてから手を動かすべきでした。
  • たとえば前述の「動作するきれいなコード」のところでは、ふたつめの for 文のところで、
for symbol in"ハート", "ダイヤ", "スペード", "クラブ"

というふうにしたくない願望が急速にわいてきて、結局 Card クラスにも手を出してしまいました。Card クラスにはまだテストを用意していないので、TDD 的にはよろしくありません。寄り道したくてもグッとこらえる忍耐力が必要なのかもしれません。

  • 開発効率が上がるので、自然と英語力の無さが浮き彫りになり普段以上に開発の足を引っ張るところ。メソッド名を決めるのに5秒以上かけたくないのに、次から次へと命名しないといけないので、あっというまに語彙が尽きて手が止まり、パートナーさんと必死に英単語をググる時間がもったいないくらいありました。

ペアプロと TDD の相性は抜群

折に触れて味わえる達成感、やった感、前進してる感。

「面白いと思ったこと」で書いた通り、ポジティブになれる要素が多いです。ペアでやればそれだけ盛り上がれます。

また、ペアプロはどこかのタイミングでドライバーを交代する必要がありますが、TDD は「テストを書く」「通す」「きれいにする」という分かりやすい小目標が常にあるので、目標達成=ドライバー交代にタイミングを摺り合せやすいのがGOODでした。

私たちの隣りのチームではペアワークが盛んです。(参考:「常時ペアワークしてたら仲間が増殖してきた話」)私たちのチームもペアワークへの信頼が厚いチームです。【TDD Boot Camp】で学んだことは両チームに持ち帰り、よりペアワークの成果と楽しさを高めるために布教活動に勤しみたい所存です。


というわけで、ペアプロ × TDD の試みはこんなチームにオススメです。

  • 普段からペアプロを推進している。
  • テストコードを書くときに辛そうな顔をしているジュニアエンジニアがいる。
  • TDD の良さを布教したくてウズウズしているシニアエンジニアがいる。

チームに色を足してみよう。Enjoy Pair TDD !