ottijp blog

Pythonでコンストラクタのキーワード付き引数をモックのアトリビュートにセットする

ORMにpeeweeを使うクラスをユニットテストしています. テストにはmockを使い,モデルのアトリビュートに正しい値をセットしているか確認しようとしましたが,モデルのコンストラクタにアトリビュートにセットするものをキーワード付き引数で渡す場合に,mock.Mockクラスそのままではうまくテストできなかったので,キーワード付き引数をアトリビュートにセットするモックを作成しました.

なお,Python歴がまだ2ヶ月なもので,大きな勘違いをしているかもしれず,もっとスマートなやり方があるのかもしれません.間違いやより良い方法を知っている方がいればコメント頂けると大変助かります.

2016/10/15追記

この記事のコードではモックが1インスタンスだけ作成されるケースにしか対応していません.複数インスタンス化されるケースは,コメントで @podhmo さんが例示してくれたコードのようなやり方が必要です.

環境

  • Python 2.7.12
  • peewee 2.8.5
  • mock 2.0.0

準備

$ pip install peewee
$ pip install mock

ディレクトリ構成

$ tree
.
├── src
│   ├── __init__.py
│   ├── model.py
│   └── service.py
└── test
    ├── __init__.py
    └── test_service.py

テスト対象コード

  • peeweeのモデルクラスを継承したmodel.HogeModelをservice.HogeServiceが利用してデータストアするサンプルです
src/service.py
# -*- coding: utf-8 -*-

from model import HogeModel

class HogeService(object):
    def run(self):
        model = HogeModel(name='hoge name')
        model.desc = 'hoge desc'
        model.save()
src/model.py
# -*- coding: utf-8 -*-

from peewee import SqliteDatabase
from peewee import Model
from peewee import PrimaryKeyField
from peewee import CharField

db = SqliteDatabase('hoge.db')

class HogeModel(Model):
    id = PrimaryKeyField
    name = CharField()
    desc = CharField()

    class Meta:
        database = db

テストコード

  • service.HogeServiceをテストするコードです
  • まずは単純にmock.patchのデコレータを使ってmodel.HogeModelをモック化してみました
  • これをテスト実行すると3つ目のテストだけNGになってしまいます
test/test_service.py
# -*- coding: utf-8 -*-

import unittest
from mock import patch
from mock import Mock
from service import HogeService

class TestHogeService(unittest.TestCase):

    @patch('service.HogeModel')
    def test_run(self, mock_hoge_model):
        mock = Mock()
        mock_hoge_model.return_value = mock
        target = HogeService()
        target.run()
        self.assertEquals(1, mock.save.call_count)  # OK
        self.assertEquals('hoge desc', mock.desc)  # OK
        self.assertEquals('hoge name', mock.name)  # NG

if __name__ == '__main__':
    unittest.main()
$ PYTHONPATH=src python test/test_service.py 
F
======================================================================
FAIL: test_run (__main__.TestHogeService)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/otti/.pyenv/versions/2.7.12/lib/python2.7/site-packages/mock/mock.py", line 1305, in patched
    return func(*args, **keywargs)
  File "test/test_service.py", line 18, in test_run
    self.assertEquals('hoge name', mock.name)  # NG
AssertionError: 'hoge name' != <Mock name='HogeModel().name' id='4454023888'>

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

コンストラクタのキーワード付き引数をモックのアトリビュートにセットする

  • テストが通らないのは,service.HogeServiceでmodel = HogeModel(name='hoge name')と書いてコンストラクタで渡されたものがモックのアトリビュートにはセットされないからでした
  • そこで,コンストラクタで渡されたキーワード付き引数をモックのアトリビュートにセットし,patchしたクラスのインスタンス化時にそのモックを返すようにしました
  • また,モデルにセットされたアトリビュートをテストコードでassertしたいので,生成したモックはテストクラスのインスタンス変数に持つようにしました
test/test_service.py
# -*- coding: utf-8 -*-

import unittest
from mock import Mock
from mock import patch
from service import HogeService

class TestHogeService(unittest.TestCase):

    def create_attr_set_mock(self, **kwargs):
        self.mock = Mock()
        # コンストラクタで渡されたキーワード付き引数をアトリビュートにセットする
        for k, v in kwargs.items():
            self.mock.__dict__[k] = v
        return self.mock

    @patch('service.HogeModel')
    def test_run(self, mock_hoge_model):
        # 対象クラスのインスタンス化時にコンストラクタのキーワード付き引数をアトリビュートにセットするモックを返す
        mock_hoge_model.side_effect = lambda **kwargs: self.create_attr_set_mock(**kwargs)
        target = HogeService()
        target.run()
        self.assertEquals(1, self.mock.save.call_count) # OK
        self.assertEquals('hoge desc', self.mock.desc) # OK
        self.assertEquals('hoge name', self.mock.name) # OK

if __name__ == '__main__':
    unittest.main()
 PYTHONPATH=src python test/test_service.py 
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK

これで期待する動作をするモデルのモックが作れました. mock.Mockにはそもそもnameというアトリビュートがあるようなので少し紛らわしい例でしたが,name以外のアトリビュートで試しても同様でした.

参考


ottijp
都内でアプリケーションエンジニアをしています
© 2026, ottijp