mockを理解するまでpytestがかなり苦痛だった

アイキャッチ画像

はじめに

Pythonでpytest(Pythonのテストフレームワーク)を使い始めた頃、正直かなり苦手意識がありました。

特に辛かったのが「mock(モック)」です。

最初は、

「なんでこんな複雑なことしてるんだ?」

と思っていました。

普通に関数を呼べばいいように見えるのに、

  • patch
  • MagicMock
  • return_value
  • side_effect

など、見慣れないものが大量に出てきます。

しかも、エラーが出ても原因が分かりにくいです。

私はAWS Lambdaの開発でpytestを書くことが多かったのですが、S3やDynamoDBなどAWSサービスとの連携があるため、mockを避けて通れませんでした。

今回は、mockを理解するまでに苦労したことや、「ここを理解して少し楽になった」というポイントについて書いてみたいと思います。

最初は「なんでわざわざmockするの?」状態だった

最初の頃は、

「実際に動かせばよくない?」

と思っていました。

例えば、S3へアップロードする処理があるなら、

  • 実際にS3へアップロード
  • Lambdaを実行
  • 結果確認

すればいいように感じていました。

ただ、実務ではこれを毎回やるのはかなり大変です。

例えば、

  • テストが遅い
  • AWS環境必要
  • 権限問題がある
  • コストがかかる
  • ネットワーク依存になる

などがあります。

さらに異常系確認になるともっと大変です。

例えば、

  • S3アップロード失敗
  • DynamoDB更新失敗
  • タイムアウト
  • 例外発生

などを毎回本当に再現するのは現実的ではありません。

そこで使われるのがmockでした。

mockを使うことで、

「実際にはAWSへアクセスせず、擬似的に動作させる」

ことができます。

patch対象が分からなかった

mock初心者の頃、特に苦労したのが patch です。

例えば、

@patch("boto3.client")

のようなコードがあります。

ただ最初は、

「なんでここ文字列なん?」

状態でした。

しかも、patch対象を少し間違えるだけで動きません。

例えば、

@patch("boto3.client")

では動かず、

@patch("sample_module.boto3.client")

で動くことがあります。

最初はこの違いが全然分かりませんでした。

実際には、

「どこでimportされたものを差し替えるか」

が重要なのですが、初心者の頃はかなり混乱しました。

MagicMock地獄

mockを書いていると、よく MagicMock が出てきます。

ただ最初は、

「これ何者なん?」

という感じでした。

しかも設定不足だと、

<MagicMock name='mock.xxx'>

みたいな値が大量に出てきます。

特に辛かったのが、戻り値のmockです。

例えば、

mock_client.return_value.get_item.return_value

のようなコード。

最初見た時、

「return_value多すぎるだろ…」

と思いました。

さらにネストが増えるとかなり読みにくくなります。

context managerのmockがかなり難しかった

個人的にかなり苦戦したのが、with 文のmockです。

例えば、

with pd.ExcelWriter(path) as writer:

のような処理。

これをmock化する際、

mock_writer.return_value.__enter__.return_value

のような書き方が必要になります。

最初は意味が分かりませんでした。

「なんで __enter__ とか出てくるんだ…」

とかなり混乱しました。

ただ後から調べると、with 文は内部的に __enter____exit__ を呼んでいることを知りました。

こういうPython内部仕様を知るきっかけになったのは面白かったです。

side_effectを理解してかなり楽になった

mockを使う中で、かなり便利だと思ったのが side_effect です。

例えば、

mock_s3.upload_fileobj.side_effect = Exception("upload error")

と書くと、意図的に例外を発生させられます。

これにより、

  • 異常系テスト
  • リトライ確認
  • エラーハンドリング確認

などがかなりやりやすくなりました。

実務では正常系より異常系の方が重要なことも多いので、これはかなり助かりました。

最初はmockを「面倒なもの」と思っていましたが、理解してくると、

「現実では再現しづらいケースを簡単に試せる」

便利さを感じるようになりました。

mockを書き始めてから設計を見る目も変わった

面白かったのが、mockを書いていると「テストしやすい構造」を意識するようになったことです。

例えば、

  • 関数が長すぎる
  • AWS依存が強すぎる
  • 責務が混ざっている

コードは、mockもかなり書きづらいです。

逆に、

  • 処理分割されている
  • 外部依存が切り離されている
  • 関数責務が明確

なコードはテストもしやすいです。

つまり、mockを書いていると自然と設計改善にもつながっていきます。

これは実際にやってみて気づいた部分でした。

まとめ

pytestを触り始めた頃、mockはかなり苦痛でした。

特に最初は、

  • patch意味不明
  • return_value多すぎ
  • MagicMock怖い
  • エラー読めない

という状態でした。

ただ、実務でAWS連携や外部サービス処理を扱う中で、mockは避けて通れませんでした。

そして少しずつ理解していくと、

  • 外部依存を切り離せる
  • 異常系を簡単に試せる
  • 手動試験を減らせる
  • 安心して修正できる

というメリットがかなり大きいことに気づきました。

今でもmockは簡単だとは思っていません。

ただ、「最初はみんな苦戦するものなんだな」とは感じています。

もし今pytestやmockで苦しんでいる人がいれば、まずは

「全部理解しようとしすぎない」

ことをおすすめしたいです。

実際、私自身も実務で使いながら少しずつ慣れていきました。