tshimizu's diary

日々の記録

DIPとモックによるテストの手習い

はじめに

最近ちょこちょこテストを書くようになった。その中で、うまいテストの書き方がなかなか見つからないことも多い。ここで言う「うまい」というのは、テストしたい対象の外側に対して依存がなく(少なく)なるような書き方のことを指している。

うまく書けない例としては、データベースが絡むパッケージに関するテストが挙げられる。この「データベースが絡む」の範囲が、必要以上に広がってしまうせいで、本来はデータベースをあまり考えたくないパッケージのテストを書くにしても、データベース関係の処理を書く必要が出たりする。理想的には、外部環境に依存しない形でパッケージごとのテストを書きたい。もちろん、実際のデータベースを使わなければ、稼働時とは異なる状態での動作になる。しかし、外部環境を考えないテストであっても、対象パッケージのロジック自体の確認にはなるし、本来はそれが対象パッケージへのテストとしてはある意味正しいような気もする。

そんなことを考えている状態で、書籍『Clean Archtecture -達人に学ぶソフトウェアの構造と設計-』を年始に読んで、問題を解決する方法が見つかった(かもしれない)。書籍全体としては、まだ理解しきれていない部分も多いが、後述する「依存関係逆転の原則」に関しては納得できたつもりなので、ここにまとめておく。

依存関係逆転の原則(DIP: Dependency Inversion Principle)

クリーンアーキテクチャの基本的な考えとして、「変化しにくいもの(内部)に変化しやすいもの(外部)を依存させるべき。」という考えがある。すごく単純化して例を挙げると、変化しにくいものと言うのはビジネスのルールとかアプリケーションのロジックの部分で、変化しやすいものと言うのはデータベースとフレームワークとかの外部要因など。

つまり、データベース特有の処理などに、アプリケーションのロジックが引っ張られてはだめということ。データベースのよう外部要因に関する部分は、いつでも取り替えることができて、アプリケーションのロジックのコードを変更はしたくない。このような理想的な状態を、DIPによって生み出すことができる。

アプリケーションを何も考えず単純に書いた場合、図1のようになる。アプリケーションのロジックが書かれた「Service Package」から、データベースに関する処理が書かれた「Data Package」に対して参照がある。参照があるというのは、コード上での依存が存在することを意味する。

f:id:fcimsb55yn23:20190105151523p:plain
図1シンプルな構成

この依存関係を逆転させたい。つまり、変化しやすい「Data Package」が変化しにくい「Service Package」に対して依存するようにしたい。これを実現するために、DIPでは図2のようにインターフェースを利用する。「Service Package」側でインターフェースを定義して、パッケージ内のコード上ではそのインターフェースを使った処理を書く。そして、「Data Package」では「Service Package」のインターフェースを実装する。このような設計にすることで、パッケージという単位で見ると、処理の流れはそのままだが、依存関係は「インターフェース実装」という形で逆転している。

f:id:fcimsb55yn23:20190105151531p:plain
図2DIPを用いた構成

適用例

前述のパッケージ構成を再現した、Goによるサンプルコードを載せる。パッケージの構成は以下のようになっている。

└── simple
    ├── data
    │   └── user_service.go
    └── service
        ├── user_service.go
        └── user_service_test.go

└── dip
    ├── data
    │   └── user_service.go
    └── service
        ├── user_service.go
        └── user_service_test.go

シンプルな構成(simple)

simple package

DIPを用いた構成(dip

dip package

DIPを使って構成された方は、「service」パッケージに関するテストが、外側に依存しない形でかけていることがわかる。このテストで検証しているのは、「service」パッケージのロジックそのものだけになっている。実際には裏でデータベースが起動しているような外側の構造については、モデル化されたモックを実装している( UserRepositoryMock )。

参考