質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

1回答

14829閲覧

python3unittestで、クラスのインスタンス変数をpatchでmock化したい

siruku6

総合スコア1382

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

0グッド

0クリップ

投稿2019/04/28 13:33

編集2019/05/02 05:49

models/client.py 内のクラスのインスタンス変数hogeをmock化したいです。
tests/test.py にテストコードを書いています。

ディレクトリ構成

app/
├ models/client.py

└ tests/test.py

ソースコード

大体こんな感じなのだろうなと予想をしております。

python

1# client.py 2class Client(): 3 def __init__(self): 4 # ...色々省略しています... 5 self.hoge = # 外部APIに対するrequestのresponse結果が代入される 6 7 def get_hoge(self): 8 result = # self.hoge をここでごちゃごちゃといじる 9 return result 10 11 # 以下インスタンスメソッドが続きます 12 13 14# test.py 15import unittest 16from unittest.mock import patch 17import models.client as client 18 19class TestClient(unittest.TestCase): 20 def setUp(self): 21 self.client_instance = client.Client() 22 23 def test_get_hoge_method(self): 24 with patch('models.client.???', ???=???): 25 hoge = self.client_instance.get_hoge() 26 self.assertEqual(hoge, 望ましい結果, 'get_hogeがresponseに対して正しく操作を行っていることを確認する')

やりたいこと

テストにおいて実際にAPIに対してrequestしたくないので、self.hogeをmockで仮想responseに置き換える。

試したこと

???と書いた部分を試行錯誤してなんとかうまく動いてはいるのですが、ものすごく遠回りをしていて、**実際誰もこんな書き方しないだろうな...**というひどいコードになってしまいました。

お願い

ですので、上記の目的を達成できる方法について、ご存知の書き方があれば複数提供いただけると大変嬉しいです。

また、unittestのpatchの書き方を上手く検索することができなかったため、何か良い検索方法をご教示いただけると大変嬉しいです。

ドキュメントの類を見ても、自分の欲しい書き方がどこにあるのか全く分かりませんでした。
たとえばここ
unittest.mock --- 入門¶

追記

インスタンス変数をmockで置き換えることができた例(すごく汚いコード)

(2019/05/02 追記)

python

1# client.py 2 3import abcd.something_api as something_api 4class Client(): 5 def __init__(self): 6 # ...色々省略しています... 7 something_api.Reqetsing(some_object) 8 self.api_response = some_object.response 9 10 def get_hoge(self): 11 # self.api_response をここでごちゃごちゃといじるのですが省略します 12 result = self.api_response['hoge'] 13 return result 14 15 # 以下インスタンスメソッドが続きます 16 17 18# test.py 19import unittest 20from unittest import mock 21from unittest.mock import patch 22import models.client as client 23 24class TestClient(unittest.TestCase): 25 def setUp(self): 26 self.client_instance = client.Client() 27 28 def test_get_hoge_method(self): 29 response_msg = 'dummy_responseです' 30 31 _mock = mock.MagicMock(response={ hoge: response_msg }) 32 with patch('abcd.something_api.Requesting', side_effect=[_mock]): 33 # client.py の `something_api.Reqetsing` も mock で置き換えた 34 with patch('***.***.***', return_value=None): 35 hoge = self.client_instance.get_hoge() 36 37 self.assertEqual(hoge, response_msg, 'get_hogeがresponseに対して正しく操作を行っていることを確認する')

メソッドの細部の書き換えや省略をしているので誤記があるかもしれませんが、ほぼこのような動きです。

インスタンス変数をmock化したい理由

mockとpatchで2回の置き換えを行っているのが不満で、どうにかこれを「1回で済ませたいな」という思いから、インスタンス変数をmock化したいと思っています。

それだけでなくインスタンス変数をmock化する方法自体も純粋に知りたいので、できればメソッドのmock化ではなく、インスタンス変数のmock化の方法を回答いただきたいです。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ご提示のサンプルがどこまで実際のコードに近いのかわかりませんが、ご提示のコードの場合だと、単純にテスト内で

python

1self.client_instance.hoge = 'hoge'

とすればよいだけではないでしょうか。 unittest.mock を使う必要が無いように見えます。

実際のコードはもっと mock を使いたくなるようなものになっているのでしょうか。もしそうなら、もう少し実際のものに近いコードをご提示いただいた方がより具体的な回答がもらいやすくなると思います。

追記 1

インスタンスアトリビュートではなく、外部 API へのリクエスト処理を mock 化したい、ということですね。

ひとつだけサンプルをお示ししてみます。

次のようなクラス Client がある場合を考えてみます。

client.py:

python

1import requests 2 3class Client: 4 URL = 'https://example.com/api/posts/' 5 6 def get_hoge(self): 7 response = requests.get(self.URL) 8 return response.content

ここで requests.get() は外部 API へのリクエストを行っていると考えてください。

この requests.get() をコンテキストマネージャで mock したい場合は、例えば次のようにします。

test_client.py:

python

1import unittest 2from unittest import mock 3 4import client 5 6class TestClient(unittest.TestCase): 7 def test_get_hoge(self): 8 with mock.patch('client.requests.get') as get: 9 get.return_value.content = 'mocked value' 10 c1 = client.Client() 11 result = c1.get_hoge() 12 self.assertEqual(result, 'mocked value')

私の手元ではこのテストは成功しますが、これでご質問への回答になっているでしょうか。あるいはもっと違ったものを求めてらっしゃるでしょうか。

何か良い検索方法をご教示いただけると

私は公式ドキュメントの unittest.mock のページ ↓ を理解できるまで読むのが結局近道だと考えていて他にこれといったものも思いつけないので、他の方からの回答を待ってみてください :D

もしかしたら、 GitHub で from unittest.mock import patch 等で検索するとよいサンプルが見つかるかもしれません:

追記 2

追記・コメントありがとうございます。

できれば、メソッドではなくて変数をmock化する方法がしりたいのですが、そういったことはできないのでしょうか?

インスタンス変数を置き換える方法を探していまして、一応実現できた方法を追記しますので、それよりももう少しマシな方法があればなぁと思っております。

このご発言から siruku6 さんが「自分が追記したコードではインスタンス変数の置き換えがなされていて、 gh640 のコードではされていない」と考えていらっしゃると私は理解したのですが、この理解は合っていますか?

もしそうなら、私は違う認識を持っています。私は siruku6 さんが追記されたコードも私が提示したコードも mock 化という意味ではやっていることは同じで「 callable を差し替えて、その戻り値のインスタンスのアトリビュートをダミー値に変えてチェックしている」と思っています。 siruku6 さんのコードでは callable が abcd.something_api.Requesting でアトリビュートが response 、私が提示したコードでは callable が requests.get() でアトリビュートが content と、それぞれ名前は違いますが構造的には同じだと私は認識しています。

いかがでしょうか。もしこの理解 ↑ が間違っているなら、私は siruku6 さんが繰り返し言われている「インスタンス変数の mock 化」が何を意味するのかがわかっていません。「インスタンス変数の mock 化」が、私のコードではされていなくて siruku6 さんのコードではなされている、と考えていらっしゃるかと思うのですが、そこをもう少し詳しくご説明いただけると、何らか追加の説明がさせていただけるかもしれません。

投稿2019/04/29 01:16

編集2019/05/02 23:45
gh640

総合スコア1407

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

siruku6

2019/04/29 04:14 編集

実際のコードを全部乗せるとややこしくなるため、省略しています。 かといって適度に複雑にするというのもなかなか難しかったため極限まで単純化してしまいました。 実際 hoge の中には、外部APIに対するrequest結果が代入されていて、その仮想レスポンスをget_hoge()関数でごちゃごちゃといじって返却する想定です。 説明不足なところがあったかもしれませんが、 「インスタンス変数をmockによって置き換えるための汎用的な方法が知りたい。」 という目的で質問しておりますので、他の方法でお願いしたいです。 上記コードにおいては確かにmockは必要ないかと思いますが、教科書類では、明らかに必要ない場所で無駄に高度な手法を使う説明がされることがあると思います。 そういう観点で回答いただけると大変ありがたいです。 ※念のため補足しておきますが、with patch(): という記法で置き換えたいです。 他の方法は今は求めておりません。
siruku6

2019/04/29 12:15 編集

追記ありがとうございます! 非常に理想に近い形になってきました。 できれば、メソッドではなくて変数をmock化する方法がしりたいのですが、そういったことはできないのでしょうか? インスタンス変数を置き換える方法を探していまして、一応実現できた方法を追記しますので、それよりももう少しマシな方法があればなぁと思っております。 ドキュメント類は、、、自分なりに頑張って読んだのですが、追記したコードが現時点の私の限界でした。
siruku6

2019/05/06 03:28 編集

>「インスタンス変数の mock 化」が何を意味するのか return_value=変数 では、メソッドを呼び出したときの返り値を指定することはできます。 が、そうではなくて、変数を呼び出したときの値を設定したいのです。 ()がついているときの戻り値ではなく、()がないときの戻り値を設定する方法とも言えます。 クラス内にインスタンスメソッドとインスタンス変数が宣言されているとして、 ・self.hoge() に対しては return_value で戻り値をmockで設定 ができますが、 ・self.hoge に対して、代入されている値(返される値)をmockで設定 をする方法が知りたいです。 return_valueだと、変数に代入されている値を操作することができなかったため、私は side_effect を使って無理やり実現したのですが、side_effect以外の方法があれば知りたいです。 ただのmockならなんだかできそうなのですが、with patchで実現する方法がなかなか見つかっていません。 # ???の部分の記述だけでなんとかしたい(side_effect以外) with patch('someclass.hoge', ???=代入する値):
gh640

2019/05/07 04:55

ありがとうございます。おかげさまで「インスタンス変数の mock 化」の意味はわかりました。 話を続ける前に 2 点確認させてください。 1. > return_valueだと、変数に代入されている値を操作することができなかったため、私は side_effect を使って無理やり実現したのですが、side_effect以外の方法があれば知りたいです。 `side_effect` についての認識について少し気になったのですが、ご提示のコードだと次の 2 行は ```python _mock = mock.MagicMock(response={ hoge: response_msg }) with patch('abcd.something_api.Requesting', side_effect=[_mock]): ``` 次の 2 行で置き換えられる(つまり、これら 2 つのコードはこの文脈では等価だ)と私は思っていますが、この認識は siruku6 さんも同じですか? ```python _mock = mock.MagicMock(response={ hoge: response_msg }) with patch('abcd.something_api.Requesting', return_value=_mock): ``` 2. ```python def __init__(self): # ...色々省略しています... something_api.Reqetsing(some_object) self.api_response = some_object.response ``` 私は、ご提示のテストコードから逆算で考えて、 `something_api.Reqetsing()` の戻り値が `some_object` だ(つまり、 `some_object` が `something_api.Reqetsing()` の引数になっているこの ↑ コードは間違っている)と私は思っていたのですが、実際どちらでしょうか。そして、もし `some_object` が `something_api.Reqetsing()` の戻り値でないのであれば、 `some_object` はどこで生成されているのでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問