はじめに
昴、お誕生日おめでとう! 今日は劇場で野球しようぜ! nikkieです。
気になっていた積み記事「“Don’t Mock What You Don’t Own” in 5 Minutes」を読みました。
目次
- はじめに
- 目次
- まとめ:「“Don’t Mock What You Don’t Own” in 5 Minutes」とは
- きっかけはちょうぜつ本
- “Don’t Mock What You Don’t Own” in 5 Minutes
- 例:Docker Repository Client
- 終わりに
まとめ:「“Don’t Mock What You Don’t Own” in 5 Minutes」とは
- モック主義TDDが主張する「Don’t Mock What You Don’t Own」の解説記事
- 例えば、HTTPライブラリを使う実装のテストでモックを使うときは、HTTPライブラリをラップする薄いレイヤーを自作して、それをモックしよう
きっかけはちょうぜつ本
ちょうぜつ本の読書会、前回は6章の前半でした。
読書会の中でモックについて、「私はよく分かっていないんですが、こういう意見もあるみたいです」と記事「“Don’t Mock What You Don’t Own” in 5 Minutes」を紹介しました。
読書会の中では参加者の方から「記事の気持ちが分かってきた」というアウトプットがあり、それを聞く中で私も気付きがあって、以下をツイート(ありがとうございました!)
#ちょうぜつ本 読書py、本日は第6章の前半、単体テストやスタブ・モックの部分。
— nikkie にっきー (@ftnext) 2023年9月1日
アウトプットを通じて色々学びがあったのですが、何の気なしに紹介した「“Don’t Mock What You Don’t Own” in 5 Minutes」、これがどういう主張か分かってきたという方に解説いただき大感謝ですhttps://t.co/SRWBEhI9lF
すると、ちょうぜつ本著者のひさてるさんよりリプライをいただきました!
ありがとうございます。
だいぶ遅レスだけど、テストとモックについてコメントを書いています #ちょうぜつ本 https://t.co/B3XhnlzCOl
— 田中ひさてる (@tanakahisateru) 2023年9月20日
リプライまでいただいたのですが、私が元記事をエアプ同然だったので、(1日1エントリを使って)まずは元記事を理解することにしました。
“Don’t Mock What You Don’t Own” in 5 Minutes
この記事は「Don’t Mock What You Don’t Own」(意訳 所有していないものをモックするな)の解説記事です。
5分で分かる「Don’t Mock What You Don’t Own」 といった趣でしょうか。
EuroPython 2022でLT版が存在します。
このLT、リアルタイムで見ていたのですが、仕事終わりの参加1で朦朧とする意識の中、主張が全然分からなかったけど、引っかかるLTとなりました。
主張が分からないというのは、私は普段(TDDで)テストコードを書く中で、記事やLTでDon'tと言われたモックの仕方を「当然こうやるでしょ」と思ってやっていたからです
Don’t Mock What You Don’t Own とは
今回記事を読んで初めて知ったのは、「Don’t Mock What You Don’t Own」はロンドン学派(=モック主義TDD)が提唱していたということです。
記事を書いたhynekさんは、これを解説していたわけですね(初見の主張だったので、ここから分かっていなかった)
記事では例を挙げて「Don’t Mock What You Don’t Own」が解説されているにすぎません。
例:Docker Repository Client
HTTP通信のライブラリを使ったPythonプログラムの例です2。
ライブラリにはhttpxが選択されています。
ビジネスロジックであるget_repos_w_tags
関数を実装しています。
def get_repos_w_tags(client): # リポジトリ一覧を取得 # 個々のリポジトリについてタグの一覧を取得
Rude Mocking(しない方がよい🙅♂️)
get_repos_w_tags
関数の引数にhttpx.Client
のモックを渡します。
httpx.Client
は利用しているだけで、所有しているわけではないですよね。
client = Mock( spec_set=httpx.Client, get=Mock( return_value=Mock( spec_set=httpx.Response, json=lambda: { "repositories": [] }, ) ), )
hynekさんがこれに否定的な理由は、client.get().json()
というビジネスロジックの表現が3つのモックになっていて分かりづらいためと理解しました。
こうなってくるとモックを作るヘルパーを書きたくなりますが、それでは解消しないと書かれています(Mock Hellの文字が...)。
Polite Mocking(こうしよう!🙆♂️)
別の方向性の解決策として、HTTPライブラリ(ここではhttpx)をラップするレイヤーを追加する方法が推奨されます。
add a very thin layer around the HTTP library, which becomes the façade between your clean code and the messy outside world.
DockerRegistryClient
クラスを爆誕させ、2つのメソッドを生やします。
- get_repos
- get_repo_tags
初期化時にhttpx.Client
インスタンスが渡るので、APIアクセスはそれを使った実装です。
DockerRegistryClient
を持っている=APIを所有している、と理解しました。
この自作クライアントを引数に渡すとして、get_repos_w_tags
はスッキリ書けます。
このとき、テストでは(httpx.Client
ではなく)DockerRegistryClient
をモックします
drc = Mock(
spec_set=DockerRegistryClient,
get_repos=lambda: []
)
Rudeな例との違いはモックが1つで済むこと!
これはdrc.get_repos()
というビジネスロジックの実装が分かりやすいです。
get_repos
と同様にget_repo_tags
もモックできます。
終わりに
「“Don’t Mock What You Don’t Own” in 5 Minutes」、やっと意味分かった〜!
ここはいつもの天使様の登場ですね。
「所有していないものをモックするTDDとか舐めているのですか」
ごめんなさい〜。
練習していこうと思います。
hynekさんの記事は情報量がすごくて、新しく知ったものがいくつもありました。
アウトプットしていただいたことに感謝です
- Pythonでスタブするライブラリ https://github.com/alex/pretend
- 『Architecture Patterns with Python』 https://www.cosmicpython.com/
- この本を読んだ方から実は「Don’t Mock What You Don’t Own」の話を聞いていたことに今さらながら気付きました(私が聞く耳を持っていなかった)
- Don’t Mock What You Don’t Ownを扱った記事の数々