Python mockの適用範囲はどこまで

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

突然ですが、きゅうりって収穫せずにほっておくとどうなるか知ってますか?

畑の土が肥えているかどうかにもよりますが、めちゃくちゃ大きくなってウリみたいになるんです。

色は黄色になるし、中身はスカスカの状態になるので、捨てるしかないかなと思って【育ちすぎたきゅうり】とネットで調べてみると育ちすぎたキュウリの料理がたくさんヒットしました。

育ちすぎたキュウリ

意外にも、キュウリを収穫し忘れてウリみたいになってしまうという私と同じ失敗をしてしまっている人が多くてびっくりしました。

ま〜、問題なく食べられるみたいなので、ネットの情報を参考に美味しくいただこうと思います。

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

昨日までの復習

昨日まで外部サービスの値を擬似的に取得できるmockについて学習してきました。

MagicMockを使ったり、デコレーターを使ったり、メインコードのメソッドをmockにしたり、クラスごとmockにしたりしていろいろなテストのやり方がありました。

学習した内容は、mockの一部ですので、もっとたくさんの使い方がありますが、実践しながら使うものを身につけていくようにしましょう。

今日は、メインのコードをどこまでmockにするのかについて考えてみましょう。

mockの適用範囲

mockは外部サービスで取得する値を外部のサービスを使わずに擬似的に値を入れてメインコードをテストするものです。

なので、基本的にメインのファイルで完結できる関数については、mockを使う必要はありませんが、mockを適用して値を入れることもできます。

この範囲をどこまで適用してもいいのかということが気になるところです。

いっそのこと、全てmockを適用して、擬似的な値を入れれば、テストコードを書くのが非常に楽になります。

とはいっても、関数が動作しているのか確認するためには、きちんとテストする必要がありますので、何でもかんでもmockにしてもいいわけではありません。

例えば、銀行などのシステムであれば、ちょっとしたバグでも大問題になってしまいますので、mockで擬似的な値を入れるのではなく、きちんとしたテストを実行する必要があります。

一方で、自分が開発した簡単なアプリなどでは、バグが見つかれば、その都度修正すれば問題はありませんので、全てmockにしてしまっても問題ないかもしれません。

どんなシステムやアプリを開発するのか、クライアントから受注したものなのかなど、状況によってmockの適用可能な範囲が変わってきます。

クライアントから、外部サービスだけmockを使うとか、外部サービスの中でもこの関数だけmockを使っても構わないなどの要求があるので、その要求に従ってテストを実行することが求められるということです。

クライアントの要求に答えるには、モックオブジェクトライブラリを熟知する必要がありますが、その数は莫大なのですべてを網羅することはできません。

すべてを網羅するのではなく、基本的なmockの使い方を理解した上で、必要に応じてモックオブジェクトライブラリを活用するのがベストな方法と言えるでしょう。

ファイル内関数をmockしてみる

ということで、メインのファイル内で処理可能な関数をmockにするコードを書いてみましょう。

これまで使っていたメインのファイルに下記の関数を付け加えて、テストファイルでmockにしてみます。

    def special_bonus(self):
        return 0

    def cal_salary(self):
        try:
            bonus = self.bonus_api.bonus_price(year=self.year)
        except ConnectionRefusedError:
            bonus = 0
        bonus += self.special_bonus()
        return self.base + bonus

13行目、14行目で新しい関数special_bonusで特別ボーナスを与える関数を追加しました。

cal_salaryには、21行目でspecial_bonusを加えた金額を出すように変更しています。

次にテストファイルにコードを付け加えます。

test_salary.py

不要なコードは削除して、Salaryクラスのspecial_bonusmockにするコードと、salary_pricemockに入れた値を合計した値になるように160に変更しました。

import unittest
from unittest import mock

import salary


class TestSalary(unittest.TestCase):
    @mock.patch('salary.BonusApi', spec=True)
    @mock.patch('salary.Salary.special_bonus')
    def test_cal_salary_class(self, mock_special, MockRest):
        mock_rest = MockRest.return_value
        mock_special.return_value = 10
        mock_rest.bonus_price.return_value = 100

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

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

10行目の関数に2つのmockの引数を入れていますが、デコレーターを2つ入れた場合は、関数の引数は、あとのほうが第一引数になります。

あとはこれまでと同じように、return.valueで値を入れて、18行目でbase50bonus100special_bonus10を足した160を入れてassertEqualで確認しています。

テストをを実行すれば、スペルミスなどがなければテストにパスします。

Mockのテスト Python

終わりがない

Pythonの学習は、ここまでやれば終了というようなものではなく、終わりがありません。

スポーツや学問も目標はあっても終わりはありません。

目標を達成できれば、不足しているところが見えてくるので、さらに学習することが出てくるわけです。

完璧を目指すのではなく、満足のいく結果を出すために、現時点でできることをやることが大切です。

Pythonのドキュメントが完璧に頭の中に入れるのは非効率ですので、必要になったときにどこから引き出したらいいのか分かればいいわけですし、使用頻度が低いコードを覚えるのは、有限であるわたしたちのリソースを無駄遣いしているのと同じです。

公式ドキュメントを辞書代わりに使って、使用頻度の高いものをしっかりと覚えるようにしましょう。

それでは明日もGood Python!