TDD再考 (6) – テスト容易性 ≠ 良いデザイン

ソフトウェア開発の世界にTDDが紹介された当時、最も説得力があり、そして魅力的に響いた格言は「テスト容易性(Testability)を追求すれば、自然に良いプログラムをデザイン出来る」というものだった。あるモジュールをユニットテストするためには、そのモジュールをシステムの他の部分から切り離して、独立して動作させる必要がある。ということは、テストしやすいモジュールは必然的に良いモジュール設計の指標である「低結合度(low coupling)」を実現することになる。

しかし、テスト容易性は必ずしも良いデザインに結びつくわけではないと、DHH氏は言う。テストをしやすくするために、過剰な「間接層(indirection)」が導入されることによって、コードの簡潔さが失われてしまうと。

過剰な間接層の例として紹介されているのが、Hexagonal architecture というデザインパターンで、アプリケーションのコアとなるドメインモデルと外部のインタフェースを明確に分離するためのテクニックである。階層化アーキテクチャの一種と言っても良い。ドメインモデルを「ポート」と呼ばれるAPI的な層(「ポート」という語はネットワークのプロトコル・ポートや電子機器の接続を行うポートを意識している)で取り囲み、さらにその外側に「アダプタ」と呼ばれる、様々な外部システムとのやり取りをポートに適合するよう変換するためのレイヤーを配置する。

Hexagonal architecture http://alistair.cockburn.us/Hexagonal+architecture

上図のシステムでは、administration, trigger data, notifications, database という4つのポートがあり、それぞれのポートに外部システムあるいはインタフェースごとのアダプタがある。

さらに今回の記事で紹介されているのは、Ruby on Rails の Controller や Active Record といったパーツを他のレイヤーに依存させずにユニットテストするためにこの Hexagonal architecture を導入するという例で、これは完全にやり過ぎであると、DHH氏は批判しているわけである。

それぞれのレイヤーを単独でテスト出来るようにするため、Controller からは直接 Active Record にアクセスさせずに Repositoryパターンを経由するようにし、Controller内のロジックは Commandパターンとして切り出す。つまり、ロジックを Rails に依存しないモジュールに配置して、それらを Rails から切り離して高速にテストできるようにするためのテクニックである。

DHH氏は、Hexagonal architecture自体を非難しているわけではなく、多種多様な外部システムに対応しなければならないシステムで役に立つ可能性については認めている。問題にしているのは、ただ「ユニットテストをやりやすくするため」だけの目的でこの複雑な構造を導入することについてである。つまり、間接層を導入してモジュールの置換可能性を高めるのは、機能的な要求があれば合理的な選択となるが、ただユニットテストのためだけにこれを導入するのは、犠牲にされたプロダクトコードの簡潔さとその見返りの釣り合いが取れないのではないかという指摘だ。

そもそもControllerの責務は、ユーザーからのリクエストとモデルからのレスポンスを統合することなのだから、ユニットテストではなく結合テスト(Integration test)で対応するべきだ、というのもなかなか説得力がある。そのように考えると、システムの中でユニットテストの対象となる部分はかなり限定されることになりそうだ(ユニットテストの定義をどう捉えるかによるが)。ドメインモデルのテストについても、データベースから切り離してテストしようとするのは時代遅れであると切り捨てている。これは筆者も同意する部分で、モデルにおいてはデータベースとのやり取りがむしろ一番重要なところであり、昨今のORMではインメモリデータベースを利用して簡単かつ比較的高速に結合テストが実施出来るため、これをやらない理由はほとんど無いように思える。

さて、TDD陣営の人たちはこの過剰な間接層問題に無頓着だったのだろうか? 過去の資料を当たる限りそんなことはなさそうだ。Martin Fowler氏による有名な書籍『リファクタリング』の中に、Kent Beck氏による「間接層とリファクタリング」というコラムがある。

「コンピュータサイエンスは、間接層(indirection)を設けることであらゆる問題が解決できるという信念に基づいた学問である。― Dennis DeBruler」

TDDに欠かせないプラクティスであるリファクタリングは、つまるところプログラムに間接的な部分を加えるということである。この間接層というものの重要性を強調しつつも、

しかし、間接層はもろ刃の剣であることに注意しなければなりません。1つのものを2つに分割するということは、それだけ管理しなければならない部分が増えるということなのです。また、オブジェクトが、他のオブジェクトに委譲を行って、その先もさらに委譲を繰り返すような場合、プログラムが読みにくくなるのも事実です。つまり間接層は、最小限に絞り込むべきなのです。

TDD再考 (7) – テストはどこまで書けば良いのか?


TDD再考 – これまでのコンセプトマップ

これまでの資料まとめ - https://oinker.me/room/marubinotto/TDD
これまでの資料まとめ – https://oinker.me/room/marubinotto/TDD

“TDD再考 (6) – テスト容易性 ≠ 良いデザイン” への2件のフィードバック

  1. いま参加しているプロジェクトが、クリーンアーキテクチャを導入して進行中です。ただしテスト時にモックはまったく使わず、実DBに繋ぎにいきます。目的としては、明確に整理された構造を持つことで、コードの見通しや保守性を高めるといったことになると思います。

    まだはじまったばかりなので、評価するには早いですが、個人的にいまのところ一番辛いのは、やはりテストが重いことですね。これはご指摘の通りインメモリにすれば速くなるはずなので、試して提案しようと考えているところです。

    いいね

    1. 武藤さん、コメント有り難うございます。

      > 個人的にいまのところ一番辛いのは、やはりテストが重いことですね。

      フレームワークだと ORM + In-Memory database による自動テストの枠組みが既に用意されていたりしますが、それでもテスト時にフレームワーク自体をロードしないといけないので、完全モックの場合よりは若干重いですよね。テストの数が増えた時にどこまで許容出来るのか、プロジェクトの性格によって変わると思うので、その辺が興味深いところです。テストの実行速度に対する許容度が案外低いという話を多く耳にしますが、私はどちらかというと結構我慢出来るタイプです(笑) それよりもウチのプロジェクトでは Provisioning を CI しているのですが、そちらの遅さはかなり気になっています。

      いいね

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト /  変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト /  変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト /  変更 )

%s と連携中