2018年7月16日月曜日

Python3 の unittest で Mock を使ってみる

概要

unittest は Python3 に標準で搭載されているテストモジュールです
簡単なクラスを作成してテストしてみました

環境

  • macOS 10.13.5
  • Python 3.6.5

テスト対象のコード

  • vim mock.py
import requests
import json

class TestClient(object):
    def req_get(self):
        return json.loads(requests.get('https://kaka-request-dumper.herokuapp.com/').text)

    def req_post(self):
        return json.loads(requests.post('https://kaka-request-dumper.herokuapp.com/').text)

普通のテストコード

  • vim test_mock.py
import unittest
from mock import TestClient


class TestMockClass(unittest.TestCase):

    def test_get(self, mock):
        cli = TestClient()
        res = cli.req_get()
        self.assertEqual(res['method'], 'GET')

    def test_post(self):
        cli = TestClient()
        res = cli.req_post()
        self.assertEqual(res['method'], 'POST')


if __name__ == '__main__':
    unittest.main()
  • python3 -m unittest test_mock.py

これでテストすると実際にサーバにアクセスしてテストします
これを Mock 化してサーバにアクセスしないで各メソッドをテストします

Mock 化する

  • vim test_mock.py
import unittest
import json
from mock import TestClient
from unittest.mock import patch, MagicMock


class TestMockClass(unittest.TestCase):

    @patch('mock.requests')
    def test_get(self, mock):
        mock_res = MagicMock(text=json.dumps({'method': 'GET'}))
        mock.get.return_value = mock_res
        cli = TestClient()
        res = cli.req_get()
        self.assertEqual(res['method'], 'GET')

    @patch('mock.requests')
    def test_post(self, mock):
        mock_res = MagicMock(text=json.dumps({'method': 'POST'}))
        mock.post.return_value = mock_res
        cli = TestClient()
        res = cli.req_post()
        self.assertEqual(res['method'], 'POST')


if __name__ == '__main__':
    unittest.main()

説明

MagicMock クラスを使ってモックを作成します
ポイントは以下の 2 行です

mock_res = MagicMock(text=json.dumps({'method': 'GET'}))
mock.get.return_value = mock_res

MagicMock でモックが返却するレスポンスを作成します
今回は requests.get が JSON を返却して、その JSON 内の method というキーをチェックします
request.get.text で JSON 文字列が受け取れなければいけません
なので MagicMock(text=json.dumps({'method': 'GET'})) という感じでレスポンスを作成します
text=request.get.text の最後の .text になります
.text を参照したときに JSON 文字列が返ってきますよという宣言になります
そして作成したレスポンスを mock.get.return_value = mock_res に設定します
mock オブジェクトはテスト用の関数 (test_get) の引数として受け取っています
これは何かと言うとモック化した requests になります
@patch('mock.requests') のデコレータを付与することでそれを実現しています
mock オブジェクト (requests) の get の return_value を先ほど作成したモックレスポンスとして設定しています

こうすることで実際にサーバにアクセスすることなくサーバと同等のレスポンスを返却する Mock を定義することができます

  • python3 -m unittest test_mock.py

これで実行すると実際にサーバにアクセスせずテストが成功することが確認できると思います

Tips

1 つのテスト中で複数のメソッドやクラスに対して @patch を当てることも可能です
例えば今回のサンプルであれば

    @patch('mock.requests')
    @patch('mock.json')
    def test_get(self, jmock, reqmock):

こんな感じで複数のモジュールに対してモックを当たられます
複数のモックを受け取るためにテストに対して複数の引数を指定しましょう
上に指定したモックが後ろの引数に入る点に注意しましょう

最後に

unittest の Mock 機能を使ってみました
基本的にはレスポンスを Mock でエミュレートして擬似的なレスポンス情報を返却させる感じだと思います
MagicMock には他にもたくさんの機能があるので詳細は参考サイトにある公式のページを参考にしてください

参考サイト

0 件のコメント:

コメントを投稿