今回のお題
今回はpythonのmockライブラリの基本的な使い方について取り上げます。
目次
- mockとは何か
- 具体例
- 終わりに
mockとは何か
mockとはテスト内で用いる代替品、及びそれを簡単に用意するためのpythonの標準ライブラリのことを指します。
例えばあるプログラムに、戻り値を返すメソッドA
とメソッドAを呼び出すためのメソッドB
を実装する予定だして、先にメソッドBが完成したとしましょう。
この場合、メソッドAの完成を待ってからメソッドBをテストするというのはあまり望ましくありません。
そんな悠長なことをしていてもしメソッドBに不備があった場合、メソッドAの実装後だと修正が大変になる可能性があるからです。
ですがメソッドBのテストにはメソッドAが必要なので、暫定的にメソッドAの代わりをしてくれるものを作り、それを呼び出せるかどうかを以てメソッドBのテストとする、というのが一般的です。
このメソッドAの代わり
のことをプログラミングの世界ではmockと言います。
また、pythonにはこのmockを簡単に用意できるライブラリが用意されており、そちらもmockと呼ばれています。
具体例
具体的に見ていきましょう。
以下が実装予定のプログラムです。
class Calculator(object):
def add(self, a, b):
return (a + b)
def sub(self, a, b):
return (a - b)
def mul(self, a, b):
return a * b
def div(self, a, b):
return (a // b)
from calc import Calculator
class CalculatorLibrary(object):
def __init__(self):
self.calc = Calculator()
def calculate(self, input_a, input_b, operation):
if operation == "+":
return self.calc.add(input_a, input_b)
elif operation == "-":
return self.calc.sub(input_a, input_b)
elif operation == "*":
return self.calc.mul(input_a, input_b)
elif operation == "/":
return self.calc.div(input_a, input_b)
else:
raise ValueError("wrong operation!")
一つ目のモジュールは四則演算の関数を定義しており、二つ目のモジュールは引数に応じて然るべき四則演算関数を呼び出す役割を持っています。
そして、二つ目のcalclib.py
のためのテストも用意しました。
from calc import Calculator
from calclib import CalculatorLibrary
import unittest
class MyTestCase(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.calc = CalculatorLibrary()
print("calclib is ready")
return super().setUpClass()
def test_add(self, mock_add):
self.assertEqual(self.calc.calculate(10, 5, "+"), 15)
def test_sub(self):
self.assertEqual(MyTestCase.calc.calculate(10, 5, "-"), 5)
def test_mul(self):
self.assertEqual(self.calc.calculate(2, 4, "*"), 8)
def test_div(self):
self.assertEqual(self.calc.calculate(8, 3, "/"), 2)
if __name__ == "__main__":
unittest.main()
ですがこの時にcalc.py
が以下の状態だったらどうでしょう。
class Calculator(object):
def add(self, a, b):
pass
def sub(self, a, b):
pass
def mul(self, a, b):
pass
def div(self, a, b):
pass
テストができないですよね。
なのでこの関数の代わり、すなわちモックを用意しなければなりません。
では、代わりとして用意するモックがどのような性質を持っていれば良いでしょうか。
例えば以下のテストの場合、
def test_add(self, mock_add):
self.assertEqual(self.calc.calculate(10, 5, "+"), 15)
- Calculatorクラスのaddメソッドが呼ばれる。
- 呼び出されたaddメソッドが15を返す
という二つをクリアできればテストは成功します。
なのでCalclatorLibraryクラスのテストを行うための最低限の要件として、
- addメソッドが呼び出されるときに代わりに呼び出される
- 毎回必ず
15
を戻り値として返す
という機能を持ったモックを用意します。
# from unittest.mock import patch
# ...省略...
@ patch(Calculator, "add")
def test_add(self, mock_add):
mock_add.return_value = 15
self.assertEqual(self.calc.calculate(10, 5, "+"), 15)
# ...省略...
patchデコレータ
でどのクラスのどのメソッドを模倣するのかを指定し、mock_xxx.return_value
でその模倣した関数の戻り値を指定します。
今回確かめたいのはあくまでも関数の呼び出しがうまくいくかどうかなので、これでも十分にテストの要件を満たします。
終わりに
以上がmockの使い方でした。
使い方そのものよりも、その場でテストしなければならないのはどの部分で、逆にどの部分はテストしなくても大丈夫かという切り分けが大事かなと感じました。