Python mock side_effect

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

まだ最終目標には届きませんが、200日継続できたというのは、かなり感慨深いものがあります。

これまでは頑張っても100日程度だったので、結構がんばれてるんじゃないかと思います。

目標はまだ先ですが、気持ちを引き締めて学習していこうと思います。

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

昨日の復習

昨日は、関数を使って疑似値をわたすことができるside_effectを学習しました。

mock.patchなどでは、外部サービスから受け取る疑似値を直接入力して渡していましたが、渡す値を関数の返り値で受け取ることもできました。

簡単な関数であれば、直接lambdaを使って代入することもできました。

mockに関数を使った値を渡すside_effectについては、こちらの記事をごらんください。

今日は、関数を使って疑似値を返せるside_effectを使ってエラーの処理やリストを利用したテストを学習します。

test_salary.py

テストするメインのファイルはこれまでと同じsalary.pyをつかいます。

テストファイルは昨日までと同じですが、外部サービスがダウンしていてアクセスできないときなどのエラーであるConnectionRefusedErrorが返されたときのコードに変更しています。

import unittest
from unittest import mock

import salary


class TestSalary(unittest.TestCase):
    def setUp(self):
        self.patcher = mock.patch('salary.BonusApi.bonus_price')
        self.mock_bonus = self.patcher.start()

    def tearDown(self):
        self.patcher.stop()

    def test_cal_salary_patch_side_effect(self):

        self.mock_bonus.side_effect = ConnectionRefusedError

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

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

ハイライトしている17行目でConnectionRefusedErrorside_effectを使ってmock_bonusに代入しています。

メインファイルのcal_salaryがエラーコードに対応していないため、テストを実行するとエラーを返します。

Python mock side_effect

テストでメインファイルの修正が必要だとわかったので、メインファイルのcal_salaryを修正します。

cal_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)
        try:
            bonus = self.bonus_api.bonus_price(year=self.year)
        except ConnectionRefusedError:
            bonus = 0
        return self.base + bonus

コメントアウトしている行をtry-exceptを使ってConnectionRefusedErrorが起こったときに、bonus0にするコードに変更しています。

関数の計算結果は50 + 050になります。

このあと、テストファイル(test_salary.py)の22行目の値を150から50に変更すれば、テストはパスします。

リストで値を入れる

side_effectは、リストで値を複数回返してテストすることができるので、外部サービスを呼び出すのが1回だけじゃなく複数回呼び出して結果が違う場合などに使うことができます。

    def test_cal_salary_patch_side_effect_list(self):

        self.mock_bonus.side_effect = [
            100,
            200,
            300,
            ValueError('Bankrupt')
        ]

        s = salary.Salary(year=2020)
        salary_price = s.cal_salary()
        self.assertEqual(salary_price, 150)
        self.mock_bonus.assert_called()

        s = salary.Salary(year=2020)
        salary_price = s.cal_salary()
        self.assertEqual(salary_price, 250)
        self.mock_bonus.assert_called()

        s = salary.Salary(year=2020)
        salary_price = s.cal_salary()
        self.assertEqual(salary_price, 350)
        self.mock_bonus.assert_called()

        with self.assertRaises(ValueError):
            s.cal_salary()

test_salary.pyに新しい関数を追加しました。

27行目でside_effectValueErrorを含めた4つの値を代入しているので、合計はそれぞれ150250350ValueErrorとなり、テストは問題なくパスします。

テストは大切

Pythonを学習し始めた最初のうちは、メインのファイルだけ作ればいいのですが、自分以外の大勢の人が使うようなプログラムを作るようになったら、間違いなくテストは必要です。

自分だけが使うのなら、バグがあっても直せばいいだけですが、大勢の人が使うプログラムを提供するにはバグが無いかしっかりとチェックする必要がありますので、テストは非常に重要です。

テストのコードを書くことによって、メインのファイルで不足しているところがわかることもあるので、自分以外の人に使ってもらえるプログラムがかけるようになったら、テストも並行して実行するようにしましょう!

それでは、明日もGood Python!