unittestのMock~実践編~

記事タイトルとURLをコピーする

はじめに

PE部の谷です。
先日、unittestの超基本を学び、実践で書いてみたいと思っていたら機会がありました。

unittestとは

Pythonコードのテストを行う上で有用な機能が実装されたモジュールのこと。

unittest、テストコードって何だろう?という方は、以前書いた初心者向けのブログがあるのでそちらをご覧ください。 blog.serverworks.co.jp

詳細については、公式ドキュメントを参照してください。

docs.python.org

テスト対象コード

blog.serverworks.co.jp

今回、テストを行うのは、こちらのブログで紹介したZendeskの組織情報を更新するコードの一括更新の部分です。

executable.py

def bulk_update(updated_info_list):
   """
   一括更新
   """
   try:
      job_status_list = []
      size = 100
      for start in range(0, len(updated_info_list), size):
         job_status = zenpy_client.organizations.update(
            updated_info_list[start: start + size]
         )
         job_status_list.append(job_status)
      return job_status_list
   except Exception as e:
        logger.exception('例外が発生しました: %s', e)

テストコード

import unittest
import executable
from unittest.mock import MagicMock, patch
 
 
class TestZendeskMethods(unittest.TestCase):

    @patch('executable.zenpy_client')
    def test_bulk_update(self, mock_zenpy_client):
        mock_job_status = {
            "job_status": {
                "id": "111111111111111111111111111111111111111",
                "url": "https://zendesk.com/api/v2/job_statuses/111111111111111111111111111111111111111.json",
                "total": 3,
                "progress": 3,
                "status": "completed",
                "message": "Completed at 2021-10-27 03:05:55 +0000",
                "results": [
                    {"action":"update","status":"Updated","success":True,"id":222222222222},
                    {"action":"update","status":"Updated","success":True,"id":333333333333},
                    {"action":"update","status":"Updated","success":True,"id":444444444444}
                ]
            }
        }
        mock_zenpy_client.organizations.update.return_value = mock_job_status
        less_100_result = executable.bulk_update(list(range(0,77)))
        more_100_result = executable.bulk_update(list(range(0,1540)))
        self.assertEqual(len(less_100_result), 1)
        self.assertEqual(len(more_100_result), 16)
        self.assertNotEqual(less_100_result, more_100_result)
        with self.assertRaises(Exception):
            executable.bulk_update()        

test_bulk_update関数

渡されたリストの要素数によって意図した動きをしてくれるかをテストします。
要素の数が100件以上の時、100件未満の時、結果の要素数が幾つになるかを比較します。

ここで厄介なのは、外部APIを呼び出しているzenpy_client.organizations.updateです。
結果が接続先に依存しているため、この依存を切り離さなければいけません。  

任意の戻り値を返すようにします。
unittest.mock.patchをデコレーターで利用して、'executable.zenpy_client'を引数に指定することでexecutable.pyで使っているzenpy_clientをモックに置き換えます。
置き換えたモックはtest_bulk_updateの引数mock_zenpy_clientで受け取り、関数内で使えるようにします。
mock_zenpy_client.organizations.update.return_value = mock_job_statuszenpy_client.organizations.updateの戻り値をダミーのレスポンスに置き換えています。
ダミーレスポンスはmock_job_statusで定義しています。

まとめ

モックを使って外部APIとの依存を切り離すことができました。