PythonでMockを使ったユニットテスト

PythonでMockを使ったユニットテストを書いてみる。

Python3.3以降は標準でMockが使えるようだけど、Python2.7系では標準では入ってないのでインストールする。

$ pip install mock

サンプルコードは以下に。

# -*- coding:utf-8 -*-

import unittest
from mock import Mock, patch


# DBにつなぐのでテストのときは使いたくないクラス
class HogehogeDao(object):
    def __init__(self):
        pass

    def find(self, param_1, param_2):
        print('DBにつなぐよ')
        print('find ' + param_1 + param_2)


# テストの対象となるクラス
class CalculationModel(object):
    def __init__(self, dao):
        self.__dao = dao

    def execute(self):
        return self.__dao.find('hoge', param_2='foo')

# 使い方
calc = CalculationModel(HogehogeDao())
calc.execute()

# DBにつなぐよ
# find hogefoo
# と表示される。

# ------------------------------ ここからユニットテスト --------------------------#


class SideEffectTestResult(object):
    """side_effectの返り値用
    """
    pass


class CalculationModelTestCase(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def dataProvider(self, param_1, param_2):
        # 返り値を置き換える関数と同じ数の引数でないといけない。
        return SideEffectTestResult()

    def test_execute_1(self):
        """Mockインスタンスに置き換えてのテスト
        """
        mock_dao = Mock()  # Mockの作成
        # findメソッドとその返り値を作成
        mock_dao.find.return_value = 'testtest'
        # テスト対象のインスタンスを作成
        test_obj = CalculationModel(mock_dao)
        # 返り値が変わっていることを確認。
        self.assertEqual('testtest', test_obj.execute())
        # findメソッドが1回呼ばれていることを確認。
        self.assertEqual(mock_dao.find.call_count, 1)
        # メソッドが呼ばれたときの引数を取得できる
        # 0番から呼ばれた順に格納されている
        # callオブジェクトに格納されるのでlist化
        # 配列の0番目にタプルで引数、1番目に辞書でキーワード引数が格納される
        self.assertEqual(list(mock_dao.find.call_args_list[0]),
                         [('hoge',), {'param_2': 'foo'}])

    def test_execute_2(self):
        """メソッドをMockインスタンスに置き換えてのテスト
        """
        HogehogeDao.find = Mock(return_value='method_mock_test')
        dao = HogehogeDao()
        test_obj = CalculationModel(dao)
        self.assertEqual('method_mock_test', test_obj.execute())

    def test_execute_3(self):
        """with構文とpatch()を使って置き換えてのテスト
        """
        with patch('__main__.HogehogeDao') as mock_dao:
            mock_dao.find.return_value = 'with_mock_test'
            test_obj = CalculationModel(mock_dao)
            self.assertEqual('with_mock_test', test_obj.execute())

    def test_execute_4(self):
        """side_effectを使ってメソッドを返り値にする
        """
        mock_dao = Mock()
        mock_dao.find.side_effect = self.dataProvider
        # テスト対象のインスタンスを作成
        test_obj = CalculationModel(mock_dao)
        # 返り値が変わっていることを確認。
        self.assertIsInstance(test_obj.execute(), SideEffectTestResult)

unittest.main()

クラスをmock.Mock()で置き換えることができる。

クラスを置き換えるときは、メソッド名.return_valueで返り値を設定できる。 メソッド名.side_effectだと返り値を返す関数を設定することもできる。 そのときは元のメソッドと同じ数の引数をもたないといけない。

メソッドを置き換えるときも特に大きくは変わらない。

with構文で使うときはpatch()を使う。

そんなところで、わりと簡単にできるのでおすすめ。

関連エントリ


JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

JUnit実践入門 ~体系的に学ぶユニットテストの技法 (WEB+DB PRESS plus)

改訂新版Jenkins実践入門 ――ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)

改訂新版Jenkins実践入門 ――ビルド・テスト・デプロイを自動化する技術 (WEB+DB PRESS plus)