1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

unittest.mock.patch と assert_not_called を一緒に使うのはアンチパターンかもしれない

Posted at

※ まったくPythonテスト初心者が語っていることなので、デタラメの可能性が高いです

なんとなくpatchとか使ってユニットテスト書いていたら、「バリデーションエラーのときには、APIクライアントを叩きに行かないこと」って試験を書いてpassしているのに実際にはAPI通信しにいってる!!!みたいなことになってしまった。

そんなpatchで大丈夫か? (mockについてのメモ〜後編〜) を見つけてようやく原因がわかったわけだが、わりとハマった。

サンプルコード

伝わるかはわからないけど、なんとなくサンプルコードで説明してみる。

main.py
import re

class AwsClient:
  def __init__(self, gcm_token):
    self.gcm_token = gcm_token

  def get_sns_key(self):
    print("通信するお:", self.gcm_token)


def register(gcm_token):
  if re.match("^[a-z]+$", gcm_token):
    AwsClient(gcm_token).get_sns_key()
  else:
    print("ERROR")


if __name__ == "__main__":
  register("abc")
  # => 通信するお: abc

  register("123")
  # => ERROR

で、バリデーションエラーのときのunittestは以下のように書いたとする。

test_main.py
import unittest
from unittest.mock import patch

from main import register, AwsClient

class TestMain(unittest.TestCase):
  def test_get_sns_key_is_not_called_on_validation_error(self):
    with patch("main.AwsClient") as mockClient:
      register("123")
      mockClient.get_sns_key.assert_not_called()

これは当然passする。

$ python3 -m unittest discover
.
----------------------------------------------------------------------
Ran 1 test in 0.002s

OK

何が問題か?

patch()で意図していないものをモックしていて、そいつのメソッドが呼ばれないことを確認してしまってるということに気づくのが難しい。

落ちてほしいのだけど通ってしまう
class TestMain(unittest.TestCase):
  def test_get_sns_key_is_not_called_on_validation_error(self):
    with patch("main.AwsClient") as mockClient:
      register("abc")
      mockClient.get_sns_key.assert_not_called()

"abc" のときにはget_sns_keyは呼ばれてほしいので↑はfailするはずなんだけど、passになってしまう。

passしてほしいのだけど落ちてしまう
class TestMain(unittest.TestCase):
  def test_get_sns_key_is_not_called_on_validation_error(self):
    with patch("main.AwsClient") as mockClient:
      register("abc")
      mockClient.get_sns_key.assert_called_once()

逆もまた然り。

どうするとよい?

とくに私のような初心者は、 patch.object() を使って、文字列指定ではない方法でモック対象を指定するとよさそう。

patch.objectを使う
  def test_register(self):
    with patch.object(AwsClient, "get_sns_key") as mockMethod:
      register("123")
      mockMethod.assert_not_called()
1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?