Dependency Injection
1. 目的
処理をモック化して効率よくユニットテストを実施したい
- テスト対象のモジュールが通信や外部システムに依存する処理に依存している場合、効率よくユニットテストを実施することは困難である
- そのようなテストに影響を与える処理はモック化してテスト対象のテスト自体に集中したい
2. 課題
プログラムの中で具体的な処理を記述するとモック化が困難である
- 例として以下のようなWEBサイトにアクセスし、その結果を返却するプログラムを考える
# requests(HTTP通信ライブラリ)を使用する
import requests
class Program:
def run(url):
# 引数で指定されたURLにアクセスし、取得したデータを返却する
res = requests.get(url)
return res.text
if __name__ == '__main__':
p = Program()
result = p.run('http://example.com')
print(result)
- 上記のプログラムは
Program#run
のメソッド内で直接HTTPリクエストを発行している
- この為、
Program
のユニットテストを行いたい場合、実際にHTTPリクエストを発行する必要やその為の環境を整える必要がある(例えばオフライン時は必ずテストが失敗する)
- ユニットテスト時は
Program
の挙動のテストだけに集中したいので、HTTPリクエスト部分はモックにしたい(HTTPリクエストはライブラリの責務なのでProgram
のテスト対象外)
- しかし、この実装ではHTTPリクエスト部分をモック化することは困難である
3. 解決策
モック化したい処理は別モジュールにして、プログラムの外から渡すようにする
- モック化したい処理を別モジュールに切り出し、プログラムの外部から与える形にすることで処理の差し替えを実現する(Strategyパターン/Bridgeパターンと同じ発想)
- 以下は先のプログラムのHTTPリクエスト部分を別モジュールに切り出し、
Program
のコンストラクタに与える形に修正したものである
import requests
class HttpRequest:
def get(url):
return requests.get(url)
class Program:
def __init__(self, request):
self.request = request
def run(url):
res = self.request.get(url)
return res.text
if __name__ == '__main__':
req = HttpRequest()
p = Program(req)
result = p.run('http://example.com')
print(result)
-
Program
のHTTPリクエスト部分をモック化したい場合は次のようにする(実際にはモックはテストコード側に追加するべきである)
import requests
class HttpRequest:
def get(url):
return requests.get(url)
class MockHttpRequest:
def get(url):
result = type('', (), {})
result.text = 'Year!'
return result
class Program:
def __init__(self, request):
self.request = request
def run(url):
res = self.request.get(url)
return res.text
if __name__ == '__main__':
# HTTP通信部分をモックに差し替え
req = MockHttpRequest()
p = Program(req)
result = p.run('http://example.com')
print(result) # Year!
4. メリット
任意の処理をモック化しやすくなる
- 通信や外部システムに依存する処理を別モジュールとして切り出すことで、そのような処理をモック化することが容易になる
- モック化が容易になることでユニットテストも書きやすくなるし、実行もしやすくなる
5. デメリット
プログラムの複雑性が上がる
-
Program
の例を見ればわかる通り、元は1クラスで済んでいたコードが2クラス(モックを含めれば3クラス)に増えた
- プロダクションレベルのコードであれば更に増加することは確実である
- 処理を把握しておくべきコードの増加は保守性を低下させる原因になる為、利用時はトレードオフをよく検討する必要がある
6. 注意事項
やりすぎない
- どのような処理を別モジュール化するかはよく考える必要がある
- 何も考えず全ての処理を別モジュール化するような真似をしてはならない
参考資料
関連