Pythonでunittestのmock.patchを使う

Python学習【365日チャレンジ!】197日目のマスターU(@Udemy11)です。

まだまだ暑い日が続いていますが、朝晩が少しだけ過ごしやすくなったような気がします。

日中は相変わらず暑いので、釣りに行くなら、やっぱり夜か朝ですが、大物を狙うなら朝なので、早起きして行こうと思うのですが、なかなか起きられないんですよね。

起きたときにはすでに日が昇っていて、朝マヅメのチャンスには間に合わず、結局釣りに行けなかったなんて日もあります。

かたや気合を入れてバッチリ準備していくと全く反応がなかったり。。。

こういうところが釣りにハマってしまう原因かもしれませんね。

それでは、今日もPython学習をはじめましょう。

昨日の復習

昨日はmock.assertの使い方を学習しました。

mockを使うことで、外部サービスの処理をせずに仮想の値を代入してテストができましたが、mock.assertでは、外部サービスを使うメインの関数がきちんと呼ばれているかどうかを確認することができました。

mock.assertの詳細については、こちらの記事をごらんください。

今日は、デコレーターでmock.patchを使う方法を学習します。

salary.py

メインのファイルは昨日と同じsalary.pyです。

import requests

class BonusApi(object):
    def bonus_price(self, year):
        req = requests.get('http://localhost/bonus', params={'year': year})
        return req.json()['price']

class Salary(object):
    def __init__(self, base=50, year=2020):
        self.bonus_api = BonusApi()
        self.base = base
        self.year = year

    def cal_salary(self):
        bonus = self.bonus_api.bonus_price(year=self.year)
        return self.base + bonus

test_salary.py

今回のテストファイルでは、MagicMockを使わずにデコレータを使って、引数にテストするメソッドと返り値を指定します。

import unittest
from unittest import mock

import salary


class TestSalary(unittest.TestCase):
    @mock.patch('salary.BonusApi.bonus_price', return_value=100)
    def test_cal_salary_patch(self, mock_bonus):
        s = salary.Salary()
        self.assertEqual(s.cal_salary(), 150)
        mock_bonus.assert_called()

今回は、MagicMockではなく、mockをインポートしています。

8行目でデコレーターmock.patchにファイルのパスと返り値を指定して、9行目でテスト関数の引数に変数mock_bonusを指定します。

10行目と11行目はcal_salaryの計算をテストしていて、12行目で引数のmock.bonusを使ってbonus.priceが呼ばれているかどうかをテストしています。

ブレークポイントを入れてデバックしてみるとmock.bonusbonus_priceMagicMockが入っているのがわかります。

Python mock.patchを使ってテストをする

test_salary.pyその2

次に、デコレーターに返り値(return_value=100)を入れずに他の書き方を使って同じことをテストしてみます。

import unittest
from unittest import mock

import salary


class TestSalary(unittest.TestCase):
    @mock.patch('salary.BonusApi.bonus_price')
    def test_cal_salary_patcher(self, mock_bonus):
        mock_bonus.return_value = 100

        s = salary.Salary(year=2020)
        salary_price = s.cal_salary()

        self.assertEqual(salary_price, 150)
        mock_bonus.assert_called()

8行目のデコレーターでは、テスト関数を指定していますが、返り値(return_value)を指定せず、10行目にmock_bonus.return_value = 100を入れています。

12行目と13行目がメインのコードの処理をしている部分で、15行目、16行目でテストを行っています。

mockの処理が上下に分かれているため、通常は、上下1行ずつ空白行を入れるのがルールとされているようです。

答えは1つじゃない

基本的にどのような動作をさせるにしても、結果にたどり着くまでの方法は1つしかないわけじゃありません。

単純な計算にしても、足して5になる数字はなにかという問題があれば、1と4でもいいし、2と3でもいいし、1が3つと2が1つでも構わないわけで、-1と6でもいいわけです。

つまり、結果にたどり着く方法はいろいろな方法があって、答えが1つなわけではありません。

答えが必ずある前提の日本の教育を受けてきたわたしたちは、どうしても【正しい答え】を求めてしまいます。

正しい答えなんてものは存在しないことが分かれば、飛躍的に学習効率は上がるので、答えを求めずに自分の好奇心に従って気になったことを試してみることが重要です。

正しい答えという枠にとらわれず、未知の領域へ飛び出してみてください。

それでは明日もGood Python!