【Python】unittestの使い方 mock(モック)
この記事はPython標準のunittestの基本的な使い方についての備忘録です。
今回は、mockを使ったunittestについて記載します。
mockを使って「メソッドの戻り値を任意の値に設定する方法」と「任意の例外を発生させる方法」について記載します。
開発環境
今回は以下の環境でunittestを実装していきます。
Python 3.7.7
OS:windows10
IDE:Visual Studio Code
mockでメソッドの戻り値を任意の値にする方法
テスト対象メソッドが別のメソッドを実行し、その戻り値によって結果が変わる場合、それぞれのケースについてテストが必要となります。そんな時にmockを使用すると便利です。
呼び出し先の処理が未実装の場合などもmockを使えばスタブ化することができます。
テスト対象コード
以下のようなコードをテストします。check_number
メソッドは、return_number
メソッドを実施して、戻り値が「ゼロ」か「偶数」か「奇数」か判定し、結果に応じたメッセージを返却します。return_number
メソッドは、「0~10」のランダムな整数を返却します。
check_number
メソッドが正しく動作するかテストします。
import random
class MockSampleClass():
"""mockを使用したテストの対象クラス"""
def check_number(self):
"""ゼロor奇数or偶数をチェック"""
number = self.return_number()
if number == 0:
return "ゼロ"
elif number % 2 == 0:
return '偶数'
else:
return '奇数'
def return_number(self):
"""0~10のランダムな整数を返却"""
return random.randint(0, 10)
テストコード
各テストコードの概要は以下の通りです。
・test_number_check_zero
メソッド:return_number
メソッドの戻り値が「0」の場合
・test_number_check_even
メソッド:return_number
メソッドの戻り値が「偶数」の場合
・test_number_check_odd
メソッド:return_number
メソッドの戻り値が「奇数」の場合
from unittest import TestCase
from unittest.mock import MagicMock, patch
from mock_sample import MockSampleClass
class MockSampleClassTest(TestCase):
"""MockSampleClassのテストクラス"""
def setUp(self):
self.sample = MockSampleClass()
def test_number_check_zero(self):
"""ゼロを受け取った場合のテスト"""
mock = MagicMock()
mock.return_value = 0
with patch('mock_sample.MockSampleClass.return_number', mock):
self.assertEqual(self.sample.check_number(), 'ゼロ')
def test_number_check_even(self):
"""偶数を受け取った場合のテスト"""
mock = MagicMock()
mock.return_value = 6
with patch('mock_sample.MockSampleClass.return_number', mock):
self.assertEqual(self.sample.check_number(), '偶数')
@patch('mock_sample.MockSampleClass.return_number', MagicMock(return_value=9))
def test_number_check_odd(self):
"""奇数を受け取った場合のテスト"""
self.assertEqual(self.sample.check_number(), '奇数')
def tearDown(self):
del self.sample
mockを使用する場合は、unittest.mock
をインポートする必要があります。(コードの2行目)
使い方は、まずMagicMock
のインスタンスを生成し、return_value
に返却したい値を設定します。(コードの14,15行目)
次に該当のメソッドにmockを適用します。(コードの17行目)patch
の第一引数にmock化したいメソッドのパスを指定します。今回はテスト対象メソッドと同じモジュールの同じクラス内にあるため、「モジュール名.クラス名.メソッド名」と指定します。もし、mock化したいメソッドが別モジュール、別クラスにある場合は注意が必要です。patch
の第二引数には、コードの14行目でインスタンス化したmockの変数を指定します。
test_number_check_odd
メソッドだけ違う書き方をしています。
ここでは、メソッドの上で@patch(~)
と書くことによって、テストメソッド自体をデコレートしています。(コードの28行目)
test_number_check_zero
メソッドのような書き方をした場合、コードの17行目のwith句の中にしかmockは適用されません。そのため、with句の外でself.sample.check_number()
を実行しても、mockに指定した戻り値ではなく、mock化したいメソッドを普通に実施した結果が返却されることになります。
test_number_check_odd
メソッドのような書き方をした場合、その関数内では必ずmockが適用されます。
テスト結果
では実際に上記のテストコードを実行してみます。
$ python -m unittest tests.test_mock
..
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK
テストが成功しました。
mockを使って下位メソッドの結果に応じた判定が正しく行われていることが確認できました。
mockで例外を発生させる方法
例外発生時の動作をテストする際、実際に例外を発生させるのが難しい場合があると思います。そんな時にもmockを使用すれば、任意の例外を発生させることができます。
テスト対象コード
以下のようなコードをテストします。
calc_something
メソッドは、calc
メソッドを実施して、その結果を返却します。calc
メソッドではZeroDivisionError
が発生する可能性があり、ZeroDivisionError
が発生した場合は、エラーメッセージを返却します。
calc_something
メソッドが正しく動作するかテストします。
class MockExceptSampleClass():
"""mockを使用した例外処理のテスト対象クラス"""
def calc_something(self):
"""何らかの計算をさせて結果を返却"""
try:
answer = self.calc()
except ZeroDivisionError as e:
# エラーメッセージを返却
return e.args[0]
return answer
def calc(self):
"""何らかの計算をする。ZeroDivisionErrorが発生することもある"""
pass
テストコード
test_calc_something_except
メソッドとtest_calc_something_decorate
メソッドはどちらも同じテストを行っています。
test_calc_something_except
メソッドではメソッド内でmockオブジェクトを作成しています。test_calc_something_decorate
メソッドではpatchデコレータを使用しています。
from unittest import TestCase
from unittest.mock import MagicMock, patch
from mock_except_sample import MockExceptSampleClass
class MockExceptSampleClassTest(TestCase):
"""MockExceptSampleClassのテストクラス"""
def setUp(self):
self.sample = MockExceptSampleClass()
def test_calc_something_except(self):
"""例外発生時のテスト"""
mock = MagicMock()
mock.side_effect = ZeroDivisionError('エラーだよ')
with patch('mock_except_sample.MockExceptSampleClass.calc', mock):
self.assertEqual(self.sample.calc_something(), 'エラーだよ')
@patch('mock_except_sample.MockExceptSampleClass.calc', MagicMock(side_effect=ZeroDivisionError('エラーだよ')))
def test_calc_something_decorate(self):
"""patchデコレータを使用したテスト"""
self.assertEqual(self.sample.calc_something(), 'エラーだよ')
def tearDown(self):
del self.sample
例外を発生させる場合はside_effect
に発生させたい例外を設定します。任意のエラーメッセージも設定可能です。(コードの15行目)
with句でmockを適用します。(コードの17行目)
patchデコレータを使用する場合は20行目のように記述します。
テスト結果
では実際に上記のテストコードを実行してみます。
$ python -m unittest tests.test_mock_except
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
テストが成功しました。
mockを使って例外発生時の動作が確認できました。
まとめ
今回はunittestでのmockの使い方について記載しました。
戻り値に任意の値を設定する場合はreturn_value
に設定し、例外を発生させたい場合はside_effect
に設定します。
mockには他にも使い方があるので、使いこなせるようになりたいですね。
参考文献
実際に作業を実施した際には、以下のサイトを参考にさせていただきました。
・公式ドキュメント(version 3.7)
https://docs.python.org/ja/3.7/library/unittest.mock-examples.html
unittestの使い方については、他にもいろいろ記載しています。
よろしければ見ていってください。
ディスカッション
コメント一覧
まだ、コメントがありません