「TDD」と「テストコード」。同じ“テスト”でも全然違います〜エンジニアが語る技術愛 #04〜

「TDD」と「テストコード」。同じ“テスト”でも全然違います〜エンジニアが語る技術愛 #04〜

ミクシィには、探究心溢れるエンジニアがたくさん在籍しています。

その探究心は業務で扱う技術にとどまらず、趣味で書いているプログラムだったり、個人的に研究している言語だったりと、自身の気になった技術への追求も留まることを知りません。

そこで、社内のエンジニアに“好きな技術”について、思う存分に語ってもらうシリーズを始めました。

ルールはこの通り。

・業務で使っている技術でも、使われていない技術でもOK
・あくまでも個人的な見解で
・その技術のどこが面白いのか
・愛を込めて語り尽くしてもらう

第4回目は、新規タイトルの開発を行うクライアントエンジニアの井本に、プログラムの開発手法の一つ「TDD」と混同されがちな「テストコード」について語ってもらいました。

井本 大登(いもと ひろと)
2018年3月、株式会社ミクシィに中途入社。新規開発部署やコトダマンでのクライアントエンジニア件スクラムマスターを経験したのち、現在は新規事業にてクライアントエンジニアを務める。趣味でゲーム作ったり、技術書を執筆したりしてる。
Twitter @いも (@adarapata)

 

Unity界隈でも浸透しつつあるテストコード

━━今回はどんな技術について語っていただけるのでしょうか。

テストコードとテスト駆動開発(Test-Driven Development)、通称TDDについてです。

━━それは何ですか。

TDDについてはこの記事でも紹介されていますが、一言で説明すると、“テストコードを利用して良いコードを模索する開発手法”です。良いコードとは何なのかという話を始めると、なんだか哲学の話になってしまいますが(苦笑)、究極、その状況において最適な実装ができるものだと思っています。しかし、TDDの話をする前に、まずはテストコードとは何かについて触れる必要があります。

━━テストコードとは何でしょうか?

テストコードは、端的にいえばコードの動作を確認するプログラムの一種です。書いたコードが想定通りの挙動をしていることを担保するために書くプログラム。例えば、僕がAという処理を行うコードを書くときに、「Bができれば、Aは正しいコードだと言える」と考えたとします。これを確認するために、「Aの挙動を確認するためのプログラム」を書くことになるでしょう。これを場当たりに書いて実行してテストが終わったら捨てるというのもありですが、大体の言語においてこれを「テストコード」という形で実装して管理・実行できるツールがあります。UnityだとUnityTestRunnerがこれにあたります。

━━テストコードはUnity開発にもよく取り入れられてるのでしょうか?

Unity界隈で使われるようになったのは、直近数年ではないでしょうか。というのも、ゲーム業界自体にテストコード文化というものがあまりなかったと聞いています。家庭用ゲーム機でプレイするコンシューマーゲームは開発して一度パッケージを販売してしまうと、オンラインゲームではない限り基本的にはメンテナンスやアップデートを行うことはないので、何度もコードの修正を行うことや、メンテナンス性の高いコードを書くという必要はそこまでありませんでした。そこにスマートフォンゲームが登場し、定期的なアップデートを行うようなゲーム開発の流れが生まれました。1つのゲームのコードを幾度も改修、機能追加を行うようになったので保守性を意識しないと現場はレガシーコードで疲弊してしまいます。また、Web系の企業がスマートフォンアプリ向けゲーム開発に参入したことによって、元々Web系エンジニアの界隈に存在していたテストコードを書く文化がゲーム業界の文化と混ざりあい、今まさに少しずつ浸透していっている最中だと思います。

━━Unity界隈で使われるようになったのは、比較的最近のことなのですね。テストコードにはどのようなメリットがあるのでしょうか。

テストコードのメリットは、不具合を早い段階で発見できることです。一般的に、不具合が発生した時の改修コストは、発見が遅れるほどに上がっていきます。リリース前に見つかるのとリリース後に見つかるでは改修の影響範囲やユーザーへの対応含め大きな差があります。逆に言えばQA段階ならもっとコストが下がり、手元での動作確認時ならさらに下がり、コードレビュー段階ならさらに…と発見が早ければ早いほど改修コストは下がります。テストコードは開発段階で不具合を発見することに貢献してくれます。もちろんコード実装量が増えるので短期的に見ると時間はかかってしまいますが、中長期的にみると大きなメリットになり得ます。

テストコードを書くかどうかは、サービスやプロダクトの規模とシステムの複雑さ、リリースまでのスピード感、そしてビジネス的な判断が必要だと言えるでしょう。個人的には、書く・書かないの0か1かではなく、どこを書くべきでどこで手を抜くかという視点で考える方がよいと思います。

 

「テストコード」は開発における“ハーネス”

━━テストを書くことで不具合が早く見つかりやすくなるというのは安心できますね。

そう、安心なんです。テストコードが書かれていることで、少なくとも該当箇所は想定通りの動作が保証されます。これはエンジニアが不具合を見つける際に、問題を切り分けるための重要な情報です。「たった1行コードを追加したら様々な個所が動かなくなってしまった」、そんな不幸な状況にエンジニアは何度も遭遇します。この時にどこが原因なのか、上のコードなのか下のコードなのかはたまた別のクラスなのか、そもそもこれ以外に動かなくなっている個所があるのでは、という問題個所の特定をしなくてはなりません。ここを明らかにするのが難しい場合、僕らは怖くて「修正すると壊れそうなので動くコードに触れたくないです」となりがちです。そんなときにテストコードが失敗するようになっていれば、どこが壊れてどこが正常なのかという問題の切り分けが行いやすくなります。

そういうわけで、テストコードは「ハーネス」とも呼ばれています。建築現場で作業員が高所作業をする際の落下制止用の安全装置ですね。安全装置のテストコードというハーネスを身に着けているからこそ、安心してコードを書けるし時には思い切った改修にも取り組めるわけです。

━━なるほど。どのプログラミング言語にもテストコードはあるのでしょうか。テストコードは、何かを開発するときに必ず用いられるものなのですか。

テストコードという概念自体は、どの言語にもあると思います。ただし、全ての開発でテストコードを書くかと言われると、そういうわけではありません。

 

━━テストコードを書くのが難しいのはどのようなケースですか。

「AはBである」といった決定的な内容であれば書きやすいのですが、複雑なものであるほど記述の難易度が上がります。また、ファジーな表現の定義は難易度が高いです。ゲーム開発だとUI的な表現部分がまさにそこにあたります。キャラクターがどのぐらいのピクセル動いただとか、エフェクトの色が赤色である、といった定義はそもそも変わりやすく、答えも明確ではありません。そのような要件に対してのテストコードは壊れやすさを孕んでいます。テストコードもまたアプリケーションコードと同じくメンテナンスが必要であるため、壊れやすいテストのメンテナンスは大変です。そのような部分はテストコードではなく外部品質のQAを行う方向に倒した方がよいかなと個人的には考えています。

テストケースを書きにくい時の一つとして「どういう仕様がいいのかわからない」という状況があります。ゲームだと特に多いもので、どうやったら面白いのかを掴むために何度も何度も書き直して実機で再生してというトライアンドエラーを繰り返すことはよくあるでしょう。それは外部品質を高めるための行為だと考えていて、テストコードを書くというアプローチは直接的ではないと思います。ビルドの高速化、特定の画面をすぐに起動できるような設計にするなど、素早くトライアンドエラーできるような改善アプローチを取るのも有効だと思います。

 

一歩ずつ進めていくのがTDD

━━テストコードが何なのか、メリットは理解できました。ではTDDとはどのような開発手法なのでしょうか?

TDDでは、まずはじめに、要件のテストコードを書きます。これを「テストファースト」というのですが、このテストコードを実行させて失敗することを確認します。失敗を確認したらこのテストが通るようにアバウトなコードを実装する。テストが通れば、先ほどのアバウトなコードをきれいなコードにリファクタリングしていく。リファクタリングでテストが失敗すれば、またテストが通るようにきたないコードを実装していきます。これをひたすら繰り返して動作するきれいなコードを実装していくのがTDDです。

━━問題なく動いているかどうかはどう判断するのでしょうか?

一般的なテストツール上でテストコードを実行すると、エラーが出た場合は赤、成功の場合は緑で表示されるんです。なので、全て緑になればOKです。エラーが出た時はコードを書き直して、再度テストを実行し、緑になればまたリファクタリングを繰り返す…このサイクルが、TDDの基本的なサイクルです。こうしたサイクルは「レッド・グリーン・リファクタリング」と呼ばれています。

━━慎重に進める開発手法、という感じですね。

まさにそうですね。1歩ずつ進めることで、コードをあまり寄り道させることなくシンプルな構造にできます。ケースバイケースではありますが、一般的には多機能ではない単一な機能を持ったコードは良いコードであると考えられており、それを積み重ねていくようなイメージです。 

━━ちなみに、井本さん自身はゲーム開発で、TDDを行ったことはありますか。

 普段から割とやったりやらなかったりします。コミットログからは見えにくいですが(笑)また、『コトダマン』の開発で、同僚とペアプログラミングをやっていたときにも使いました。その方はTDDやテストコード自体も書いたことがないので、教えながら実践してみたところ、「頭がスッキリする」と言っていましたね(笑)。

━━「頭がスッキリする」とは?

TDDには、コードを書く前に、テストコードを書く「テストファースト」という特徴があります。実装のゴールが明確になっているので、このゴールを達成するために何をすべきか、と頭の中が整理されるのでしょうね。でも僕は、テストファーストができないからといって、TDDではないということはないと思っています。例えば、既存のサービスを改修する際はすでにコードがあるでしょうから、テストコードを先に書くのは物理的に不可能ですね(笑)。それよりも、これから自分がどのような処理を実装しようとしているのか、何をゴールとするのかが明らかになっていることが重要です。それがテストコードという形で表れているだけだと考えています。足元を固めながら進んでいるようなイメージです。

 

テストコードが体重計、TDDは運動や食生活の改善

━━TDDとテストコード、それぞれの特徴が見えてきたように思います。この2つの一番大きな違いは何ですか。

そもそも別物ですが敢えて違いを挙げるなら、TDDはレッド・グリーン・リファクターのサイクルで進めているのに対して、テストコードにはリファクターの要素が入っていないことです。テストコードは、品質を担保するものであって、品質を向上させるものではないのです。なので、テストコードをいっぱい書いても、内部のコードを書き換えない限り、品質は上がらないんですよね。テストコードはよく体重計に例えられます。そのときの体重が量れるだけで痩せるわけではない。TDDは、結果をもとに、実際に食生活を改善したり、運動をしたりしてリファクターを行い、また体重を量るというサイクルを繰り返しながら、健康を目指していく方法にあたります。

━━つまり、良いコードを書くためには、全てのシステム開発にTDDを導入した方がいいということでしょうか。

TDDは開発手法の一つに過ぎないので、強制する必要はないと思っています。例えるなら、正しいお箸の持ち方を強制しているようなイメージです。良いコードを書く自分なりの手法があるなら、それを使えば良いし、そこを統一する必要性はありません。

 それから、テストコードやTDDは、エンジニアが恩恵を受けるためのものであるということが大前提です。エンジニアが意味を感じられないのに、無理に使う意味はないと思っています。

━━「エンジニアが恩恵を受けるためのもの」とは、どういう意味ですか。

「テスト」と一言で言っても実際はテストコードの話だけではない様々なテストがあり、それぞれなんのために行うのかという目的が異なります。アジャイル開発におけるテストには、何を目的とした、誰のためのテストなのかをチームの支援か製品の批評、ビジネス面か技術面かの2軸で表した「アジャイルテストの4象限」という考え方があります。

▲アジャイルテストの4象限を表した図

TDDとテストコードは、チームの支援と技術面に特化した第一象限(Q1)に該当にします。つまり、エンジニアがコードを書いていく上でより実装しやすく、不具合に気づきやすく、安心して書けること目的としたテストと考えています。もちろんテストコードだけ書いていれば安心ということはなく、それぞれの象限にあたるテストも行っていくことがビジネス的には必要です。

━━なるほど。井本さんは社内外の勉強会で、TDDやテストコードについて、情報発信をしていると聞きました。それも、エンジニアが恩恵を受けられることにつながっているのでしょうか。

そうですね。僕はWeb系の企業からミクシィに転職して来ました。社内やほかのゲーム開発企業の方と交流していると、「テストコードって、なんとなくは知っているけど、書いたことはないな」と言う声をちらほら聞いて、テストコードの啓蒙活動を始めました。TDDの本質である、「より良いコードを書くために、テストコードを書くこと」を、みんなにもっとカジュアルにやって欲しいので、まずはテストコード自体への理解を深めて欲しいと思ったのです。テストコードを書こうとすると時間がかかるので敬遠されてしまいがちです。僕の師匠は「テストコードは、実務コードの3倍は書く」とおっしゃっていました(苦笑)。

━━3倍も…!

ええ。ですが、その結果、要件や仕様への理解も深くなるし、何か問題があったらまずテストが落ちて気付ける。テストコードが書きにくい、落ちやすいという時に何故そうなっているのか?この実装方法は少し怪しいんじゃないか?といった臭いを感じ取りやすくなります。

今のチームメンバーも皆さんテストコードを書いてくれていますが、落ちたテストをキッカケにここ見直さないとねといったissueを立ててくれたりと、テストを活用しているように感じています。僕は冗談交じりにテストおじさんと呼ばれたりしてますが(笑)

よいテストコードは次へのフィードバックを与えてくれるので、テストコードにかけたコストのメリットはエンジニアにも直接返ってくるんです。最近では情報発信の一例として、最近はUnityでTDDハンズオンも行っています。

https://github.com/adarapata/UnityTDD_Example

TDDの資料、ハンズオン自体は多く存在しますが、Unityを使っている人向けというものはほとんど存在しません。TDD自体が言語・フレームワーク問わず普遍的なものであるからUnity向けという言葉自体が少し適切ではないのですが(苦笑)

とはいえ、実際にコードを書いている人からすれば自分の技術領域に引っかかる範囲に資料やノウハウが存在したほうが学ぶキッカケになりやすいかなと考え、UnityでTDDをやるというテーマで公開しています。内容としてもUnityでのゲーム開発でありそうな実装にフォーカスしています。サンプル2の「自作のボタンを作ろう」などはまさにその一例かと。

こういう活動を繰り返すことで、いつの日かUnity界隈でテストを書くという行為が当たり前になると良いなあと思ってます。

━━最後に、今後挑戦してみたいことを教えてください。

直近だとUnityでテストコードを書きやすくする仕組みが欲しくて。例えばUnity Test Runnerだとテストフィクスチャを用意するのがちょっと面倒なんですよね。Ruby on RailsのFactoryBotのような簡単にテストデータを定義できるようなライブラリが欲しいなと思ったりしてます。UnityはScriptableObjectがあるので、なんかいい感じに出力できるといいのかな、とか。

あと、テストとは離れますが挑戦といえば個人でSteamでゲーム出したいですね。テストの話をたくさんしましたが、一番好きなのはゲーム開発で、ハッカソンとかゲームジャムで小さなミニゲームをよく作っています。最近だと「東方ゲームジャム」という1週間でゲームを作るお祭りでシューティングゲームを作りました。

https://www.freem.ne.jp/win/game/26519

小さい1発ネタゲームばかり作っていましたが、もうちょっとボリュームのある面白いゲームを作ってリリースしていきたいですね。

PAGE TOP