0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

メモ〜mockの使い方

Posted at

今回のお題

今回はpythonのmockライブラリの基本的な使い方について取り上げます。

目次

  • mockとは何か
  • 具体例
  • 終わりに

mockとは何か

mockとはテスト内で用いる代替品、及びそれを簡単に用意するためのpythonの標準ライブラリのことを指します。

例えばあるプログラムに、戻り値を返すメソッドAとメソッドAを呼び出すためのメソッドBを実装する予定だして、先にメソッドBが完成したとしましょう。

この場合、メソッドAの完成を待ってからメソッドBをテストするというのはあまり望ましくありません。

そんな悠長なことをしていてもしメソッドBに不備があった場合、メソッドAの実装後だと修正が大変になる可能性があるからです。

ですがメソッドBのテストにはメソッドAが必要なので、暫定的にメソッドAの代わりをしてくれるものを作り、それを呼び出せるかどうかを以てメソッドBのテストとする、というのが一般的です。

このメソッドAの代わりのことをプログラミングの世界ではmockと言います。

また、pythonにはこのmockを簡単に用意できるライブラリが用意されており、そちらもmockと呼ばれています。

具体例

具体的に見ていきましょう。

以下が実装予定のプログラムです。

calc.py
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)
calclib.py
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のためのテストも用意しました。

python.tests.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が以下の状態だったらどうでしょう。

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の使い方でした。

使い方そのものよりも、その場でテストしなければならないのはどの部分で、逆にどの部分はテストしなくても大丈夫かという切り分けが大事かなと感じました。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?