※ まったく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()