はじめに
pythonでテストをする際に、どのような場面でモック(mock)を使えばいいのかという考え方をまとめていきます。
ポイントは、以下の通りです。
- 最初にpythonでのユニットテスト実行方法を簡単にみていきます。
- その後、モックの簡単な使い方を見ていきます。
- そして、実際のテストを想定した場面でモックを使う方法をみていきます。
目次
- ユニットテスト実行の考え方
- モックとは何か?
- モックの使い方 概要
- モックの書き方 基本
- モックの書き方 ちょっと一息
- モックの書き方 応用その前に・・・
- モックの書き方 応用
ユニットテスト実行の考え方
モックの使い方を見ていく前に、最初にユニットテストの実行方法について簡単にみていきましょう。
以下の図で示したように、通常pythonでプログラムを作成する場合、外部のライブラリ(モジュール)をimport文によって使えるようにします。
テストを実行する際には、テストを実行するスクリプトを作成します。そして、そのテストを実行スクリプトから外部のライブラリ(モジュール)をimportします。
図で示すと以下のようになります。つまり、テストを実行する対象のスクリプトは、import文で呼び出すことができる状態になっている必要があるということです。
テストを実行するスクリプトでは、どのような動作になっているかを示しているのが以下の図です。
テスト実行スクリプトでは、外部からimportしたライブラリを実際に実行します。
そして、外部のライブラリを実行した結果として得られるリターン値が期待通りのものであるかどうかの判定を行います。
モックとは何か?
先ほど以下の図で示したように、テスト実行時には、外部ライブラリをimportし実際に実行します。
例えば、時刻を取得する場合は、datetimeをimportすればスクリプトが実行した時の時刻は取得できるでしょう。
しかし、テストを実行する際に指定した時刻の値を取得するようにさせるにはどうしたらよいのでしょうか。
その際に使われるものがモックというものです。図で示すと以下のようになります。
上記の図で示したように、テストを実行するスクリプトで最初に外部のライブラリをimportします。
その後、importしたライブラリを実行した際に、取得させたい値をモックによって予め設定しておくのです。
つまり、モックの使用目的は、プログラムによって参照さ・れ・る側の値を前もって設定しておきたい場合となります。
モックの使い方 概要
上記でお伝えしたように、モックはプログラムによって参照さ・れ・る側の値を予め設定しておく際に使用されます。
例えば、外部のスクリプトで以下のようなスクリプトファイルを書いたとします。
gaibu.py
def keisan():
kekka = 1 + 1
return kekka
上記のスクリプトは、以下のようにimportして実行することができます。
test_zikkou.py
import gaibu
kekka = gaibu.keisan()
上記のようにgaibuとkeisan()をimportすれば、リターンされる値は「2」となります。
今回は、上記の場面でモックを使うことにより、mock_testメソッドが参照さ・れ・る際に1をリターンするようにします。
テストを実施する場面を想定すると、今回のテスト対象のスクリプトはgaibu.pyになります。その為、当然ですがテスト対象となるgaibu.pyを書き換えることはしません。その代わりにテストを実行するスクリプトtest_zikkou.pyの中でモックを使っていきます。
モックの書き方 基本
早速、モックの書き方を見ていきましょう。テスト対象となる外部のライブラリは、先ほどと同じように以下の通りとします。
gaibu.py
def keisan():
kekka = 1 + 1
return kekka
上記のkeisan()メソッドが参照さ・れ・る際に、1をリターンするようにテスト実行スクリプトでモックを使っていきます。
ポイントは、以下の通りです。
- モックのインポート(python3.3以上でビルトインされています。)
- リターンする値を1に設定したモックを作成します。
- 作成したモックを、keisa()が実行された際に参照さ・れ・るように設定します。
test_zikkou.py
import gaibu
from unittest import mock # モックのインポート
mock_keisan = mock.MagicMock(return_value=1) # リターンする値を1に設定したモックを作成
gaibu.keisan = mock_keisan # 作成したモックを、keisa()が実行された際に参照さ・れ・るように設定
kekka = gaibu.keisan()
print(kekka) # --> 1 が表示されます。
上記のテスト実行スクリプトtest_zikkou.pyを、実行すると想定通り1がリターンされ標準出力に表示されます。
モックの書き方 ちょっと一息
ここまで読まれてきて既に気づかれていると思いますが、モックを使う場面はプログラムが参照さ・れ・る場面です。これは、プログラムを実行す・る側でリターンされる値を操作できる場合は、モックを使う必要がないことを意味しています。どういうことが具体的に見ていきましょう。
テスト対象となるスクリプトが以下のように設定されているとしましょう。今回のテスト対象となるスクリプトでは、keisan()メソッドに引数が設定されています。
gaibu_hikisu.py
def keisan(hikisu01,hikisu02):
kekka = hikisu01 + hikisu02
return kekka
当然ですがこのkeisan()メソッドがリターンする値は、テストを実行す・るスクリプト側で好きなように変更することができます。以下は、テストを実行するスクリプトです。
test_zikkou.py
import gaibu
hikisu01 = 1
hikisu02 = 2
kekka = gaibu.keisan(hikisu01,hikisu02)
print(kekka) # --> 3 が表示される。
以上のように、テストを実行するからといって必ずしもモックを使わなければならないというわけではありません。テストを実行する際に、実行さ・れ・る側の値を変更したい場合にモックは有用となるのです。
モックの書き方 応用その前に・・・
早速、モックの実際的な書き方を見ていきたいところですが、その前に、前提となるテストを実行するスクリプトの書き方を簡単にみておきましょう。モックはテストを実行する際に使われますが、まずはモック無しでテストを実行するスクリプトの書き方を確認しましょう。
テスト対象となる外部のライブラリは先ほどと同じように以下の内容とします。
gaibu.py
def keisan():
kekka = 1 + 1
return kekka
次に、テストを実行するスクリプトの例として標準モジュールのunittestを使った書き方を見ていきます。
ポイントは以下の通りです。
- テスト対象のスクリプトをインポートする。
- テストを行うモジュールunittestをインポートする。
- classとdefメソッドを定義する。
test_zikkou_unittest.py
import gaibu # テスト対象のスクリプトをインポート
import unittest # テストを行うモジュールunittestをインポート
class GaibuTest(unittest.TestCase): # classとdef関数を定義
def test_gaibu(self): # classとdef関数を定義
kekka = gaibu.keisan()
self.assertTrue(kekka, 2) # kekkaが2であればTrue(OK)となります。
if __name__ == "__main__":
unittest.main()
上記のようにまず最初に、テスト対象となるスクリプトをインポートします。そして、今回はテストの手法としてunittestを使う為、unittestモジュールをインポートします。そして更に実際のテスト内容となるclassとdef関数を設定していきます。defメソッドの中で実際にkeisan()を実行し、結果としてkekkaに2がリターンされればOKとしています。
上記のtest_zikkou_unittest.pyを実行すると、以下のように標準出力されます。
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
unittestモジュールの書き方を簡単に見てみましたが、詳細な使用法などは、こちらのリンクなどをご参照ください。
モックの書き方 応用
それでは最後に、実際にテストを行う場面を想定したモックの使い方を見ていきましょう。
テスト対象となる外部のライブラリは先ほどと同じように以下の内容とします。
gaibu.py
def keisan():
kekka = 1 + 1
return kekka
そして、先ほどunittestモジュールを使って書いたテストを実行するスクリプトにモックを使っていきます。先ほどはモックを使っていませんので、テストを実行するスクリプトで2がリターンされればOKとなっていました。今回はモックを使って1がリターンされるように参照さ・れ・る側の設定を変更します。
ポイントは以下の通りです。
- gaibu.keisan()を実行する時には注意してね。と予告設定しておく。
- gaibu.keisan()を実行する時には、2ではなくて1をリターンするモック(今回はmock_keisan)を実行するように設定。
- 2ではなくて1(モックされた値)をリターンするように設定
test_zikkou_unittest_mocked.py
import gaibu
import unittest
from unittest import mock
class GaibuTest(unittest.TestCase):
@mock.patch('gaibu.keisan') # gaibu.keisan()を実行する時には注意してね。と予告設定
def test_gaibu(self, mock_keisan): # gaibu.keisan()を実行する時に、mock_keisanを実行するように設定
mock_keisan.return_value = 1 # 2ではなくて1(モックされた値)をリターンするように設定
kekka = gaibu.keisan()
self.assertTrue(kekka, 1) # kekkaが1であればTrue(OK)となります。
if __name__ == "__main__":
unittest.main()
defメソッドの1行上の@マークが使われているのを初めて見た方もいらっしゃるかもしれまん。この@マークは、デコレーターと呼ばれており、defメソッドをデコレート(装飾)する機能があります。意味合いとしては、defメソッドの中でだけ「gaibu.keisan()を実行する時には注意してね」という設定を入れています。そして、どのように注意すればよいのかが、次のdefメソッドの行で「gaibu.keisan()を実行する時に、mock_keisanを実行するように注意してね」という形になります。
上記のテスト実行スクリプトを見てもらうと分かると思いますが、実際にgaibu.keisan()をそのまま実行しています。しかし、参照さ・れ・る側にモックの設定が既に入っているために、2ではなくて1がリターンされるという結果になります。
上記のtest_zikkou_unittest_mocked.pyを実行すると、以下のようにOKと表示されます。
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
さいごに
最後までお読みいただきありがとうございました。モックの詳しい使い方に関しましては他にもサイトが見受けられましたので、そちらなどをご参考頂ければと思います。
参考文献
https://www.toptal.com/python/an-introduction-to-mocking-in-python