TDDから僕が学んだことと最近考えてること

この記事は TDD Advent Calndar の 20 日目の記事です。1 日遅れました。すいません。

記事を書くことになったきっかけ

こんなツイートをしたら

見つかってしまいました!

というわけで、最近考えてることを書きます。

何が開発を駆動するのか

TDD は日本語では「テスト駆動開発」と呼ばれるが、私たちの開発はテスト以外にも多くのものによって駆動されていると思う。実際、TDD 以外にも XDD と呼ばれるもの(X には何らかの文字が入る)は数多くある。

テスト以外に開発を駆動するものといえば、たとえば型システムだったり、Lint のような静的解析だったり、あるいはドメインモデルだったりする。

テストコードをプロダクトコードより先に書くことによってそのコードを利用する側の立場になって開発対象を捉えるように努めるというのは TDD の大きな特徴のひとつ(もっとも本質的と言ってもよい)だけど、たとえばインターフェースを満たす仮のコードを書いた上で(TDD では「仮実装」「Fake it」と呼ばれる)REPL を起動してそれを実行するというような手順でも似たようなことは実現できる。実際、小さなアプリケーションやプロトタイピングの場合、僕はそうすることも多い。(この場合、テストコードは何度か REPL で使い心地を確認して「これでよさそうだな」って感覚が得られたタイミングでその時点で実装済みのコードに対して後から書く。厳密なテストファーストではない。*1

さて、このようなことをやっていると「果たして自分はテストに駆動されていると言えるのだろうか?」と思ってしまうことがある。TDDBC で TA をやることもある僕としては、自分が TDD をやってないのにそれを人に偉そうに語ってもいいのだろうかみたいな、ある種の背徳感というか申し訳なさみたいなのがあり、このような記事を書くにいたった。

それが冒頭のツイートの背景で、 そのツイート通り TDD とは一体何だったのかということを最近よく考えている。こんな記事を書いておいてなんだけど、実はまだ明確な答えは出ていない。

ケント・ベック氏が『テスト駆動開発入門』(原著名: 『Test Driven Development: By Example』)を書いた頃とはソフトウェアの社会的役割も大きくなってきているし、ハードウェアやソフトウェアの技術的スペックも向上していて、あの書籍に書かれていることやこれまで正しいと言われてきたことが今でも正しいのかどうかというのは自分の直面している問題領域と照らし合わせて常に考えていかなければならないし、もちろん彼自身もそう思っているはずだと思う。

テスト駆動開発

テスト駆動開発

  • 作者:Kent Beck
  • 発売日: 2017/10/14
  • メディア: 単行本(ソフトカバー)

Test Driven Development: By Example (Addison-Wesley Signature Series (Beck))

Test Driven Development: By Example (Addison-Wesley Signature Series (Beck))

  • 作者:Beck, Kent
  • 発売日: 2002/11/08
  • メディア: ペーパーバック

そう思ったときに、僕が TDD を通して教わった変わらぬ考えがあるとしたら以下のようなことなのかなと思う。

  • ユーザの立場になって考えること
  • 動作するコードをドキュメントとして残すこと
  • ソフトウェアを改善し続けること

「これらが TDD の本質だ」と言うつもりはなく、あくまで僕が TDD を通して学んだことと捉えて欲しい。

ソフトウェアのユーザとは誰か

「ユーザの立場になって考える」ということは、僕は TDD の最大の特徴なのではないかと思う。

ここで言うユーザとは、そのソフトウェアのエンドユーザのことでだけでなく、あなたが書いたコードを利用するすべてのを人を指す。むしろ、TDD の文脈におけるユーザとは多くの場合、他の開発者(将来の自分自身を含む)のことだろう。 いわゆるエンドユーザの立場で実装/実行されるテストは UI を通して行う E2E テストしかないし、後ほど言及するテストがドキュメントになるというのも、多くの場合、エンドユーザ向けではなく開発者向けのことが多い。

「ソフトウェアのユーザは誰か?」という質問をしたらみなさんは「何を馬鹿な質問をしてるんだ。ユーザはユーザだよ。」と思うかもしれないが、僕たちが作っているソフトウェアを利用している人は僕たち自身が考えているより多い。僕たちだってソースコードあるいは実行コードという形でそれを利用しているし、僕たちの顧客やマネージャーはそれを利用してお金を稼ごうとしている。「ユーザ」という言葉に違和感があるなら「ステークホルダ」と言い換えてもよい。『Lean Architecture』では、ソフトウェアのステークホルダーといは以下の 5 ロールがあると書かれている。

  • エンドユーザ(end users)
  • ビジネス(the business)
  • 顧客(customers)
  • ドメインエキスパート(domain experts)
  • 開発者(developers)

Lean Architecture: for Agile Software Development

Lean Architecture: for Agile Software Development

とにかくソフトウェアをとりまく利害関係者というものはとても多いし、ソフトウェアの設計というものは理想的にはそのすべての人たちに利益となるようなものでなければならないと思う。

このように多くのステークホルダーがいるということから考えれば、TDD というのは幾分開発者に寄ったプラクティスだとも思えるが、たとえば E2E テストを自動化しようとか、それを自然言語に近い表現で記述できるようにして顧客やドメインエキスパートにも見てもらおうといった発想ができるようになったのは、やはり TDD をやっていたからだと思っている。

ドメイン駆動設計(DDD)と TDD

プロダクトコードを先に実装してしまったが、それに対するテストコードを後から書くということも実際には結構ある。そのモチベーションとして多いのがやはり将来それを触る人へのドキュメントとしての価値を高いと感じてるからだと思う。*2

TDD のように XDD と呼ばれる手法の中で、TDD と同等かそれ以上によく聞くものに DDD (ドメイン駆動設計)というのがある。最後の D が「開発」(development)と「設計」(design)で異なるので厳密には違うんだけど、まあ TDD だって設計を駆動しているし、そこは多めに見て欲しい。

なぜ DDD の話をしたかというと、テストを後から書くケースでは、自分がドメインに駆動されてると感じることが一番多いからだ。

自分が精通している対象、過去に何度も書いたことがある分野のコードについてはテストに駆動してもらわずとも一筆書きができてしまう。だから、後からテストを書く。テストコードが自分の実装したドメインロジックの説明書のようになるわけだ。

これもテストコードの使い方としては正しいのではないだろうか。

変化のための「間」と変化の予測

Smalltalk で有名なアラン・ケイ氏が以下のように言っている。

The Japanese have a small word -- ma -- for "that which is in between" -- perhaps the nearest English equivalent is "interstitial".

http://lists.squeakfoundation.org/pipermail/squeak-dev/1998-October/017019.html

日本語訳すると「日本語には "間" という言葉がある。おそらく英語では "interstitial" が最も近い。」といったところだろうか。

アラン・ケイ氏がオブジェクト自体ではなくメッセージ(オブジェクトとオブジェクトの「間」)が重要であると強調するときに使った説明だが、この言葉は彼の発言から 20 年以上経った今でも示唆に富んでいると思う。

変化のためには「間」が必要だ。「間」のないギチギチのシステムというのは堅牢かもしれないが変化には弱い。ただ、これはオブジェクトとオブジェクトの間(あるいはシステムとシステムの間でも、システムとユーザの間でもよい)に無数にあって、それが必要となるまで意識されるべきではないものでもある。それが意図的に作られたり、意識する対象になると、複雑で意図のわからない設計物になりかねないと思う。

だからこそ、テストコードを利用する。

「間」のあるシステムは柔軟で変化に強いかもしれないが、テストコードはあくまでその時点で顕在化しているユースケースに対してのみ書かれるべきだ。そして、それを満たさなくなったとき、テストを fail させるという形で開発者にその事実を伝える。テストを先に書く、ユーザの立場になりそのコードの最初の利用者になるという TDD の定義に照らし合わせてもこれは正しい。

設計者は「こういう変更に対してはこの部分にこういうオブジェクトを追加すればよさそうだ」と考えると思うし、それ自体はよい。積極的にそうするべきだ。ただ、それは「想像」やあるいは「妄想」の範囲であって「予測」や「期待」であるべきではないと思う。要は「起こるかもしれないが、起こらないならそれはそれでよい」というメンタリティであるべきであって、その範疇を超えた予測や期待は YAGNI の原則に反するし、それが必要とされるまでは無用の複雑さをソフトウェアに持ち込むことになる。

変化のための「間」は捉えるが、変化そのものは捉えない。それがよい設計者なのではないだろうか。

そのために、想像や妄想は「それが本当に起こったときにはテストが落ちる」という形で将来の自分や他の開発者に伝えるようにし、プロダクトには「間」を見出していく、そういうテストの使い方もあるのではないかと思う。

まとめ

まとまりません。(笑)

本当はもっとちゃんと書きたかったんですが、結局ポエムみたいになってすいません。

結局、何が言いたいかというと、僕が考える TDD の好きなところは

  • 利用者の立場になること
  • テストコードが動くドキュメントになること
  • ソフトウェアを改善し続けること

であって、そのためのテストファーストであることは重要なことだけど、最近はそうでない局面もでてきたし、もう一度再考してみました、という感じです。

*1:実装済みコードをコメントアウトしたりして RED は確認するが。

*2:それでも、プロダクトに不具合を混入して RED は確認するし、テスト書いたあとにプロダクトコードをリファクタリングしたりするので、一応 TDD をやれているなとは思う。