KnowHow

技術的なメモを中心にまとめます。
検索にて調べることができます。

pythonテストコードの書き方(Moc)

登録日 :2025/07/22 19:10
カテゴリ :Python基礎

Mocをすることでテストを書くサンプル
以下のsalary.pyを、Mocを使ってテストをするサンプルコード
salary.py

import requests


class ThirdPartyBonusRestApi(object):
    def __init__(self):
        self.url = 'http://localhost/bonus'

    @staticmethod
    def get_api_name():
        return 'Bonus!'

    def bonus_price(self, year):
        r = requests.get(self.url, params={'year': year})
        return r.json()['price']


class Salary(object):
    def __init__(self, base=100, year=2017):
        self.bonus_api = ThirdPartyBonusRestApi()
        self.base = base
        self.year = year

    def calculation_salary(self):
        bonus = 0
        if self.year < 2020:
            try:
                bonus = self.bonus_api.bonus_price(year=self.year)
            except ConnectionRefusedError:
                bonus = 0
        return self.base + bonus

test_salary.py(テストコード)

import unittest
from unittest.mock import MagicMock
from unittest import mock

from test.mock import salary


class TestSalary(unittest.TestCase):
    def test_calculation_salary(self):
        s = salary.Salary(year=2017)
        s.bonus_api.bonus_price = MagicMock(return_value=1)
        self.assertEqual(s.calculation_salary(), 101)
        s.bonus_api.bonus_price.assert_called()
        s.bonus_api.bonus_price.assert_called_once()
        s.bonus_api.bonus_price.assert_called_with(year=2017)
        self.assertEqual(s.bonus_api.bonus_price.call_count, 1)

    def test_calculation_salary_no_bonus(self):
        s = salary.Salary(year=2050)
        s.bonus_api.bonus_price = MagicMock(return_value=0)
        self.assertEqual(s.calculation_salary(), 100)
        s.bonus_api.bonus_price.assert_not_called()

    def test_calculation_salary_patch_with(self):
        with mock.patch('test.mock.salary.ThirdPartyBonusRestApi.bonus_price') as mock_bonus:
            mock_bonus.return_value = 1

            s = salary.Salary(year=2017)
            salary_price = s.calculation_salary()

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

    def setUp(self):
        self.patcher = mock.patch('test.mock.salary.ThirdPartyBonusRestApi.bonus_price')
        self.mock_bonus = self.patcher.start()

    def tearDown(self) -> None:
        self.patcher.stop()

    def test_calculation_salary_patcher(self):
        self.mock_bonus.return_value = 1

        s = salary.Salary(year=2017)
        salary_price = s.calculation_salary()

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

    def test_calculation_salary_side_effect(self):
        def f(year):
            if year < 2020:
                return 1
            return 0

        # self.mock_bonus.side_effect = f
        # self.mock_bonus.side_effect = ConnectionRefusedError
        self.mock_bonus.side_effect = [1, 2, 3, ValueError('Bankrupt!!!')]

        s = salary.Salary(year=2017)
        salary_price = s.calculation_salary()
        self.assertEqual(salary_price, 101)
        s = salary.Salary(year=2017)
        salary_price = s.calculation_salary()
        self.assertEqual(salary_price, 102)
        s = salary.Salary(year=2017)
        salary_price = s.calculation_salary()
        self.assertEqual(salary_price, 103)
        s = salary.Salary(year=200)
        with self.assertRaises(ValueError):
            s.calculation_salary()

        self.mock_bonus.assert_called()

    @mock.patch('test.mock.salary.ThirdPartyBonusRestApi', spec=True)
    def test_calculation_salary_class(self, mock_rest):
        mock_rest = mock_rest.return_value
        mock_rest.bonus_price.return_value = 1
        mock_rest.get_api_name.return_value = 'Money'

        s = salary.Salary(year=2017)
        salary_price = s.calculation_salary()

        self.assertEqual(salary_price, 101)
        mock_rest.bonus_price.assert_called()